On the svn-mergeinfo-normalizer branch:
Complete documentation. No functional change.
The tool is now ready to be merged to /trunk.
* tools/client-side/svn-mergeinfo-normalizer/logic.c
(): Add missing docstrings and commentary.
git-svn-id: https://svn.apache.org/repos/asf/subversion/branches/svn-mergeinfo-normalizer@1695991 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/tools/client-side/svn-mergeinfo-normalizer/logic.c b/tools/client-side/svn-mergeinfo-normalizer/logic.c
index 1ddb665..d15e6c6 100644
--- a/tools/client-side/svn-mergeinfo-normalizer/logic.c
+++ b/tools/client-side/svn-mergeinfo-normalizer/logic.c
@@ -42,6 +42,8 @@
/*** Code. ***/
+/* Scan RANGES for reverse merge ranges and return a copy of them,
+ * allocated in RESULT_POOL. */
static svn_rangelist_t *
find_reverse_ranges(svn_rangelist_t *ranges,
apr_pool_t *result_pool)
@@ -61,6 +63,8 @@
return result;
}
+/* Scan RANGES for non-recursive merge ranges and return a copy of them,
+ * allocated in RESULT_POOL. */
static svn_rangelist_t *
find_non_recursive_ranges(svn_rangelist_t *ranges,
apr_pool_t *result_pool)
@@ -80,6 +84,8 @@
return result;
}
+/* Print RANGES, prefixed by TITLE to console. Use SCRATCH_POOL for
+ * temporary allocations. */
static svn_error_t *
print_ranges(svn_rangelist_t *ranges,
const char *title,
@@ -94,12 +100,17 @@
return SVN_NO_ERROR;
}
+/* Depending on the settings in OPT_STATE, write a message on console
+ * that SUBTREE_PATH is not mentioned in the parent mergeinfo. If the
+ * MISALIGNED flag is set, then the relative path did not match.
+ * Use SCRATCH_POOL for temporary allocations. */
static svn_error_t *
show_missing_parent(const char *subtree_path,
svn_boolean_t misaligned,
svn_min__opt_state_t *opt_state,
apr_pool_t *scratch_pool)
{
+ /* Be quiet in normal processing mode. */
if (!opt_state->verbose && !opt_state->run_analysis)
return SVN_NO_ERROR;
@@ -115,6 +126,9 @@
return SVN_NO_ERROR;
}
+/* If REVERSE_RANGES is not empty and depending on the options in OPT_STATE,
+ * show those ranges as "reverse ranges" for path SUBTREE_PATH.
+ * Use SCRATCH_POOL for temporary allocations. */
static svn_error_t *
show_reverse_ranges(const char *subtree_path,
svn_rangelist_t *reverse_ranges,
@@ -135,6 +149,9 @@
return SVN_NO_ERROR;
}
+/* If NON_RECURSIVE_RANGES is not empty and depending on the options in
+ * OPT_STATE, show those ranges as "reverse ranges" for path SUBTREE_PATH.
+ * Use SCRATCH_POOL for temporary allocations. */
static svn_error_t *
show_non_recursive_ranges(const char *subtree_path,
svn_rangelist_t *non_recursive_ranges,
@@ -155,6 +172,12 @@
return SVN_NO_ERROR;
}
+/* Show the elision result of a single BRANCH (for a single node) on
+ * console filtered by the OPT_STATE. OPERATIVE_OUTSIDE_SUBTREE and
+ * OPERATIVE_IN_SUBTREE are the revision ranges that prevented an elision.
+ * SUBTREE_ONLY and PARENT_ONLY were differences that have been adjusted.
+ * IMPLIED_IN_PARENT and IMPLIED_IN_SUBTREE are differences that could be
+ * ignored. Uses SCRATCH_POOL for temporary allocations. */
static svn_error_t *
show_branch_elision(const char *branch,
svn_rangelist_t *subtree_only,
@@ -222,26 +245,51 @@
return SVN_NO_ERROR;
}
+/* Progress tacking data structure. */
typedef struct progress_t
{
+ /* Number of nodes with mergeinfo that we need to process. */
int nodes_total;
+
+ /* Number of nodes still to process. */
int nodes_todo;
+ /* Counter for nodes where the mergeinfo could be removed entirely. */
apr_int64_t nodes_removed;
+
+ /* Number mergeinfo lines removed because the respective branches had
+ * been deleted. */
apr_int64_t obsoletes_removed;
+
+ /* Number of ranges combined so far. */
apr_int64_t ranges_removed;
+ /* Transient flag used to indicate whether we still have to print a
+ * header before showing various details. */
svn_boolean_t needs_header;
} progress_t;
+/* Describes the "deletion" state of a branch. */
typedef enum deletion_state_t
{
+ /* Path still exists. */
ds_exists,
+
+ /* Path does not exist but has not been deleted.
+ * Catch-up merges etc. may introduce the path. */
ds_implied,
+
+ /* A (possibly indirect) copy of the path or one of its sub-nodes still
+ * exists. */
ds_has_copies,
+
+ /* The path has been deleted (explicitly or indirectly via parent) and
+ * no copy exists @HEAD. */
ds_deleted
} deletion_state_t;
+/* Show the "removing obsoletes" header depending on OPT_STATE and PROGRESS.
+ * Use SCRATCH_POOL for temporary allocations. */
static svn_error_t *
show_removing_obsoletes(svn_min__opt_state_t *opt_state,
progress_t *progress,
@@ -260,6 +308,14 @@
return SVN_NO_ERROR;
}
+/* If in verbose mode according to OPT_STATE, print the deletion status
+ * DELETION_STATE for SUBTREE_PATH to the console. If REPORT_NON_REMOVALS
+ * is set, report missing branches that can't be removed from mergeinfo.
+ * In that case, show a SURVIVING_COPY when appropriate.
+ *
+ * Prefix the output with the appropriate section header based on the state
+ * tracked in PROGRESS. Use SCRATCH_POOL for temporaries.
+ */
static svn_error_t *
show_removed_branch(const char *subtree_path,
svn_min__opt_state_t *opt_state,
@@ -312,6 +368,9 @@
return SVN_NO_ERROR;
}
+/* If COPY copies SOURCE or one of its ancestors, return the path that the
+ * node has in the copy target, allocated in RESULT_POOL. Otherwise,
+ * simply return the copy target, allocated in RESULT_POOL. */
static const char *
get_copy_target_path(const char *source,
const svn_min__copy_t *copy,
@@ -327,6 +386,11 @@
return apr_pstrdup(result_pool, copy->path);
}
+/* Scan LOG for a copies of PATH or one of its sub-nodes from the segment
+ * starting at START_REV down to END_REV. Follow those copies until we
+ * find one that has not been deleted @HEAD. If none exist, return NULL.
+ * Otherwise the return first such copy we find, allocated in RESULT_POOL.
+ * Use SCRATCH_POOL for temporaries. */
static const char *
find_surviving_copy(svn_min__log_t *log,
const char *path,
@@ -335,14 +399,14 @@
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
- const char *surviver = NULL;
+ const char * survivor = NULL;
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
apr_array_header_t *copies = svn_min__get_copies(log, path, start_rev,
end_rev, scratch_pool,
scratch_pool);
int i;
- for (i = 0; (i < copies->nelts) && !surviver; ++i)
+ for (i = 0; (i < copies->nelts) && !survivor; ++i)
{
const char *copy_target;
const svn_min__copy_t *copy;
@@ -359,23 +423,27 @@
if (SVN_IS_VALID_REVNUM(deletion_rev))
{
/* Are there surviving sub-copies? */
- surviver = find_surviving_copy(log, copy_target,
+ survivor = find_surviving_copy(log, copy_target,
copy->revision, deletion_rev - 1,
result_pool, iterpool);
}
else
{
- surviver = apr_pstrdup(result_pool, copy_target);
+ survivor = apr_pstrdup(result_pool, copy_target);
}
}
svn_pool_destroy(iterpool);
- return surviver;
+ return survivor;
}
+/* Scan LOG for a copies of PATH or one of its sub-nodes from the segment
+ * starting at START_REV down to END_REV. Follow those copies and collect
+ * those that have not been deleted @HEAD. Return them in *SURVIVORS,
+ * allocated in RESULT_POOL. Use SCRATCH_POOL for temporary allocations. */
static void
-find_surviving_copies(apr_array_header_t *survivers,
+find_surviving_copies(apr_array_header_t *survivors,
svn_min__log_t *log,
const char *path,
svn_revnum_t start_rev,
@@ -406,13 +474,13 @@
if (SVN_IS_VALID_REVNUM(deletion_rev))
{
/* Are there surviving sub-copies? */
- find_surviving_copies(survivers, log, copy_target,
+ find_surviving_copies(survivors, log, copy_target,
copy->revision, deletion_rev - 1,
result_pool, iterpool);
}
else
{
- APR_ARRAY_PUSH(survivers, const char *) = apr_pstrdup(result_pool,
+ APR_ARRAY_PUSH(survivors, const char *) = apr_pstrdup(result_pool,
copy_target);
}
}
@@ -420,6 +488,18 @@
svn_pool_destroy(iterpool);
}
+/* Using LOOKUP and LOG, determine the deletion *STATE of PATH. OPT_STATE,
+ * PROGRESS and REPORT_NON_REMOVALS control the console output. OPT_STATE
+ * also makes this a no-op if removal of deleted branches is not been
+ * enabled in it.
+ *
+ * If LOCAL_ONLY is set, only remove branches that are known to have been
+ * deleted as per LOOKUP - this is for quick checks. Track progress in
+ * PROGRESS and update MERGEINFO is we can remove the info for branch PATH
+ * from it.
+ *
+ * Use SCRATCH_POOL for temporaries.
+ */
static svn_error_t *
remove_obsolete_line(deletion_state_t *state,
svn_min__branch_lookup_t *lookup,
@@ -435,6 +515,7 @@
svn_boolean_t deleted;
const char *surviving_copy = NULL;
+ /* Skip if removal of deleted branches has not been . */
if (!opt_state->remove_obsoletes)
{
*state = ds_exists;
@@ -500,6 +581,15 @@
return SVN_NO_ERROR;
}
+/* If enabled in OPT_STATE, use LOG and LOOKUP to remove all lines form
+ * MERGEINFO that refer to deleted branches.
+ *
+ * If LOCAL_ONLY is set, only remove branches that are known to have been
+ * deleted as per LOOKUP - this is for quick checks. Track progress in
+ * PROGRESS.
+ *
+ * Use SCRATCH_POOL for temporaries.
+ */
static svn_error_t *
remove_obsolete_lines(svn_min__branch_lookup_t *lookup,
svn_min__log_t *log,
@@ -513,15 +603,21 @@
apr_array_header_t *sorted_mi;
apr_pool_t *iterpool;
+ /* Skip if removal of deleted branches has not been . */
if (!opt_state->remove_obsoletes)
return SVN_NO_ERROR;
iterpool = svn_pool_create(scratch_pool);
+
+ /* Sort branches by name to ensure a nicely sorted operations log. */
sorted_mi = svn_sort__hash(mergeinfo,
svn_sort_compare_items_lexically,
scratch_pool);
+ /* Only show the section header if we removed at least one line. */
progress->needs_header = TRUE;
+
+ /* Simply iterate over all branches mentioned in the mergeinfo. */
for (i = 0; i < sorted_mi->nelts; ++i)
{
const char *path = APR_ARRAY_IDX(sorted_mi, i, svn_sort__item_t).key;
@@ -539,6 +635,9 @@
return SVN_NO_ERROR;
}
+/* Return the ancestor of CHILD such that adding RELPATH to it leads to
+ * CHILD. Return an empty string if no such ancestor exists. Allocate the
+ * result in RESULT_POOL. */
static const char *
get_parent_path(const char *child,
const char *relpath,
@@ -558,11 +657,12 @@
return "";
}
-/* Remove all ranges from RANGES where the history of SOURCE_PATH@RANGE and
- TARGET_PATH@HEAD overlap. Return the list of removed ranges.
-
- Note that SOURCE_PATH@RANGE may actually refer to different branches
- created or re-created and then deleted at different points in time.
+/* Remove all ranges from *RANGES where the history of SOURCE_PATH@RANGE
+ * and TARGET_PATH@HEAD overlap. Return the list of *REMOVED ranges,
+ * allocated in RESULT_POOL. Use SCRATCH_POOL for temporary allocations.
+ *
+ * Note that SOURCE_PATH@RANGE may actually refer to different branches
+ * created or re-created and then deleted at different points in time.
*/
static svn_error_t *
remove_overlapping_history(svn_rangelist_t **removed,
@@ -648,6 +748,12 @@
return SVN_NO_ERROR;
}
+/* Try to elide as many lines from SUBTREE_MERGEINFO for node at FS_PATH as
+ * possible using LOG and LOOKUP. OPT_STATE determines if we may remove
+ * deleted branches. Elision happens by comparing the node's mergeinfo
+ * with the PARENT_MERGEINFO using REL_PATH to match up the branch paths.
+ * Use SCRATCH_POOL for temporaries.
+ */
static svn_error_t *
remove_lines(svn_min__log_t *log,
svn_min__branch_lookup_t *lookup,
@@ -848,6 +954,8 @@
return SVN_NO_ERROR;
}
+/* Return TRUE if revisions START to END are inoperative on PATH, according
+ * to LOG. Use SCRATCH_POOL for temporaries. */
static svn_boolean_t
inoperative(svn_min__log_t *log,
const char *path,
@@ -865,6 +973,11 @@
return svn_min__operative(log, path, ranges, scratch_pool)->nelts == 0;
}
+/* Use LOG to determine what revision ranges in MERGEINFO can be combined
+ * because the revisions in between them are inoperative on the respective
+ * branch (sub-)path. Combine those revision ranges and update PROGRESS.
+ * Make this a no-op if it has not been enabled in OPT_STATE.
+ * Use SCRATCH_POOL for temporary allocations. */
static svn_error_t *
shorten_lines(svn_mergeinfo_t mergeinfo,
svn_min__log_t *log,
@@ -875,9 +988,11 @@
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
apr_hash_index_t *hi;
+ /* Skip if this operation has not been enabled. */
if (!opt_state->combine_ranges)
return SVN_NO_ERROR;
+ /* Process each branch independently. */
for (hi = apr_hash_first(scratch_pool, mergeinfo);
hi;
hi = apr_hash_next(hi))
@@ -886,9 +1001,11 @@
const char *path = apr_hash_this_key(hi);
svn_rangelist_t *ranges = apr_hash_this_val(hi);
+ /* Skip edge cases. */
if (ranges->nelts < 2 || find_reverse_ranges(ranges, iterpool)->nelts)
continue;
+ /* Merge ranges where possible. */
for (source = 1, dest = 0; source < ranges->nelts; ++source)
{
svn_merge_range_t *source_range
@@ -912,6 +1029,7 @@
}
}
+ /* Update progress. */
progress->ranges_removed += ranges->nelts - dest - 1;
ranges->nelts = dest + 1;
}
@@ -919,7 +1037,9 @@
return SVN_NO_ERROR;
}
-
+/* Construct a 1-line progress info based on the PROGRESS and selected
+ * processing options in OPT_STATE. Allocate the result in RESULT_POOL
+ * and use SCRATCH_POOL for temporaries. */
static const char *
progress_string(const progress_t *progress,
svn_min__opt_state_t *opt_state,
@@ -964,6 +1084,10 @@
return result->data;
}
+/* Depending on the options in OPT_STATE, print the header to be shown
+ * before processing the m/i at REL_PATH relative to the parent mergeinfo
+ * at PARENT_PATH. If there is no parent m/i, RELPATH is empty.
+ * Use SCRATCH_POOL temporary allocations.*/
static svn_error_t *
show_elision_header(const char *parent_path,
const char *relpath,
@@ -972,6 +1096,7 @@
{
if (opt_state->verbose)
{
+ /* In verbose mode, be specific of what gets elided to where. */
if (*relpath)
SVN_ERR(svn_cmdline_printf(scratch_pool,
_("Trying to elide mergeinfo from path\n"
@@ -989,6 +1114,8 @@
}
else if (opt_state->run_analysis)
{
+ /* If we are not in analysis mode, only the progress would be shown
+ * and we would stay quiet here. */
SVN_ERR(svn_cmdline_printf(scratch_pool,
_("Trying to elide mergeinfo at path %s\n"),
svn_dirent_join(parent_path, relpath,
@@ -998,6 +1125,10 @@
return SVN_NO_ERROR;
}
+/* Given the PARENT_MERGEINFO and the current nodes's SUBTREE_MERGEINFO
+ * after the processing / elision attempt, print a summary of the results
+ * to console. Get the verbosity setting from OPT_STATE. Use SCRATCH_POOL
+ * for temporary allocations. */
static svn_error_t *
show_elision_result(svn_mergeinfo_t parent_mergeinfo,
svn_mergeinfo_t subtree_mergeinfo,
@@ -1006,6 +1137,7 @@
{
if (opt_state->verbose)
{
+ /* In verbose mode, tell the user what branches survived. */
if (apr_hash_count(subtree_mergeinfo))
{
apr_array_header_t *sorted_mi;
@@ -1043,6 +1175,8 @@
}
else if (opt_state->run_analysis)
{
+ /* If we are not in analysis mode, only the progress would be shown
+ * and we would stay quiet here. */
if (apr_hash_count(subtree_mergeinfo))
{
if (parent_mergeinfo)
@@ -1061,6 +1195,16 @@
return SVN_NO_ERROR;
}
+/* Main normalization function. Process all mergeinfo in WC_MERGEINFO, one
+ * by one, bottom-up and try to elide it by comparing it with and aligning
+ * it to the respective parent mergeinfo. This modified the contents of
+ * WC_MERGEINFO.
+ *
+ * LOG and LOOKUP provide the repository info needed to perform the
+ * normalization steps selected in OPT_STATE. LOG and LOOKUP may be NULL.
+ *
+ * Use SCRATCH_POOL for temporary allocations.
+ */
static svn_error_t *
normalize(apr_array_header_t *wc_mergeinfo,
svn_min__log_t *log,
@@ -1161,19 +1305,23 @@
return SVN_NO_ERROR;
}
-
+/* Return TRUE, if the operations selected in OPT_STATE require the log. */
static svn_boolean_t
needs_log(svn_min__opt_state_t *opt_state)
{
return opt_state->combine_ranges || opt_state->remove_redundants;
}
+/* Return TRUE, if the operations selected in OPT_STATE require a
+ * connection (session) to the repository. */
static svn_boolean_t
needs_session(svn_min__opt_state_t *opt_state)
{
return opt_state->remove_obsoletes;
}
+/* Based on the operation selected in OPT_STATE, return a descriptive
+ * string of what we plan to do. Allocate that string in RESULT_POOL. */
static const char *
processing_title(svn_min__opt_state_t *opt_state,
apr_pool_t *result_pool)
@@ -1202,6 +1350,8 @@
return result->data;
}
+/* Sort paths in PATHS and remove all paths whose ancestors are also in
+ * PATHS. */
static void
eliminate_subpaths(apr_array_header_t *paths)
{
@@ -1226,6 +1376,9 @@
paths->nelts = dest + 1;
}
+/* If enabled by OPT_STATE, show the list of missing paths encountered by
+ * LOOKUP and use LOG to determine their fate. LOG may be NULL.
+ * Use SCRATCH_POOL for temporary allocations. */
static svn_error_t *
show_obsoletes_summary(svn_min__branch_lookup_t *lookup,
svn_min__log_t *log,
@@ -1236,9 +1389,11 @@
apr_pool_t *iterpool;
int i;
+ /* Skip when summary has not been enabled */
if (!opt_state->run_analysis || !opt_state->remove_obsoletes)
return SVN_NO_ERROR;
+ /* Get list of all missing paths. Early exist if there are none. */
paths = svn_min__branch_deleted_list(lookup, scratch_pool, scratch_pool);
if (!paths->nelts)
{
@@ -1247,6 +1402,7 @@
return SVN_NO_ERROR;
}
+ /* Process them all. */
iterpool = svn_pool_create(scratch_pool);
SVN_ERR(svn_cmdline_printf(iterpool,
@@ -1258,6 +1414,7 @@
apr_array_header_t *surviving_copies = NULL;
const char *path = APR_ARRAY_IDX(paths, i, const char *);
+ /* For PATH, gather deletion and copy survival info. */
svn_pool_clear(iterpool);
surviving_copies = apr_array_make(iterpool, 16, sizeof(const char *));
if (log)
@@ -1282,11 +1439,16 @@
deletion_rev = SVN_INVALID_REVNUM;
}
+ /* Show state / results to the extend we've got them. */
if (surviving_copies->nelts)
{
int k;
+
+ /* There maybe thousands of surviving (sub-node) copies.
+ * Restrict the output unless the user asked us to be verbose. */
int limit = opt_state->verbose ? INT_MAX : 4;
+ /* Reasonably reduce the output. */
eliminate_subpaths(surviving_copies);
SVN_ERR(svn_cmdline_printf(iterpool,
_(" [copied or moved] %s\n"),
@@ -1369,6 +1531,7 @@
const char *url;
const char *common_path;
+ /* next target */
svn_pool_clear(iterpool);
SVN_ERR(add_wc_info(baton, i, iterpool, subpool));