diff options
Diffstat (limited to 'cvs2svn_lib/fill_source.py')
-rw-r--r-- | cvs2svn_lib/fill_source.py | 192 |
1 files changed, 192 insertions, 0 deletions
diff --git a/cvs2svn_lib/fill_source.py b/cvs2svn_lib/fill_source.py new file mode 100644 index 0000000..2bb8e4c --- /dev/null +++ b/cvs2svn_lib/fill_source.py @@ -0,0 +1,192 @@ +# (Be in -*- python -*- mode.) +# +# ==================================================================== +# Copyright (c) 2000-2008 CollabNet. All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://subversion.tigris.org/license-1.html. +# If newer versions of this license are posted there, you may use a +# newer version instead, at your option. +# +# This software consists of voluntary contributions made by many +# individuals. For exact contribution history, see the revision +# history and logs, available at http://cvs2svn.tigris.org/. +# ==================================================================== + +"""This module contains classes describing the sources of symbol fills.""" + + +from cvs2svn_lib.common import InternalError +from cvs2svn_lib.common import FatalError +from cvs2svn_lib.common import SVN_INVALID_REVNUM +from cvs2svn_lib.svn_revision_range import SVNRevisionRange +from cvs2svn_lib.svn_revision_range import RevisionScores + + +class FillSource: + """Representation of a fill source. + + A FillSource keeps track of the paths that have to be filled in a + particular symbol fill. + + This class holds a SVNRevisionRange instance for each CVSFile that + has to be filled within the subtree of the repository rooted at + self.cvs_path. The SVNRevisionRange objects are stored in a tree + in which the directory nodes are dictionaries mapping CVSPaths to + subnodes and the leaf nodes are the SVNRevisionRange objects telling + for what source_lod and what range of revisions the leaf could serve + as a source. + + FillSource objects are able to compute the score for arbitrary + source LODs and source revision numbers. + + These objects are used by the symbol filler in SVNOutputOption.""" + + def __init__(self, cvs_path, symbol, node_tree): + """Create a fill source. + + The best LOD and SVN REVNUM to use as the copy source can be + determined by calling compute_best_source(). + + Members: + + cvs_path -- (CVSPath): the CVSPath described by this FillSource. + + _symbol -- (Symbol) the symbol to be filled. + + _node_tree -- (dict) a tree stored as a map { CVSPath : node }, + where subnodes have the same form. Leaves are + SVNRevisionRange instances telling the source_lod and range + of SVN revision numbers from which the CVSPath can be + copied. + + """ + + self.cvs_path = cvs_path + self._symbol = symbol + self._node_tree = node_tree + + def _set_node(self, cvs_file, svn_revision_range): + parent_node = self._get_node(cvs_file.parent_directory, create=True) + if cvs_file in parent_node: + raise InternalError( + '%s appeared twice in sources for %s' % (cvs_file, self._symbol) + ) + parent_node[cvs_file] = svn_revision_range + + def _get_node(self, cvs_path, create=False): + if cvs_path == self.cvs_path: + return self._node_tree + else: + parent_node = self._get_node(cvs_path.parent_directory, create=create) + try: + return parent_node[cvs_path] + except KeyError: + if create: + node = {} + parent_node[cvs_path] = node + return node + else: + raise + + def compute_best_source(self, preferred_source): + """Determine the best source_lod and subversion revision number to copy. + + Return the best source found, as an SVNRevisionRange instance. If + PREFERRED_SOURCE is not None and its opening is among the sources + with the best scores, return it; otherwise, return the oldest such + revision on the first such source_lod (ordered by the natural LOD + sort order). The return value's source_lod is the best LOD to + copy from, and its opening_revnum is the best SVN revision.""" + + # Aggregate openings and closings from our rev tree + svn_revision_ranges = self._get_revision_ranges(self._node_tree) + + # Score the lists + revision_scores = RevisionScores(svn_revision_ranges) + + best_source_lod, best_revnum, best_score = \ + revision_scores.get_best_revnum() + + if ( + preferred_source is not None + and revision_scores.get_score(preferred_source) == best_score + ): + best_source_lod = preferred_source.source_lod + best_revnum = preferred_source.opening_revnum + + if best_revnum == SVN_INVALID_REVNUM: + raise FatalError( + "failed to find a revision to copy from when copying %s" + % self._symbol.name + ) + + return SVNRevisionRange(best_source_lod, best_revnum) + + def _get_revision_ranges(self, node): + """Return a list of all the SVNRevisionRanges at and under NODE. + + Include duplicates. This is a helper method used by + compute_best_source().""" + + if isinstance(node, SVNRevisionRange): + # It is a leaf node. + return [ node ] + else: + # It is an intermediate node. + revision_ranges = [] + for key, subnode in node.items(): + revision_ranges.extend(self._get_revision_ranges(subnode)) + return revision_ranges + + def get_subsources(self): + """Generate (CVSPath, FillSource) for all direct subsources.""" + + if not isinstance(self._node_tree, SVNRevisionRange): + for cvs_path, node in self._node_tree.items(): + fill_source = FillSource(cvs_path, self._symbol, node) + yield (cvs_path, fill_source) + + def get_subsource_map(self): + """Return the map {CVSPath : FillSource} of direct subsources.""" + + src_entries = {} + + for (cvs_path, fill_subsource) in self.get_subsources(): + src_entries[cvs_path] = fill_subsource + + return src_entries + + def __str__(self): + """For convenience only. The format is subject to change at any time.""" + + return '%s(%s:%s)' % ( + self.__class__.__name__, self._symbol, self.cvs_path, + ) + + def __repr__(self): + """For convenience only. The format is subject to change at any time.""" + + return '%s%r' % (self, self._node_tree,) + + +def get_source_set(symbol, range_map): + """Return a FillSource describing the fill sources for RANGE_MAP. + + SYMBOL is either a Branch or a Tag. RANGE_MAP is a map { CVSSymbol + : SVNRevisionRange } as returned by + SymbolingsReader.get_range_map(). + + Use the SVNRevisionRanges from RANGE_MAP to create a FillSource + instance describing the sources for filling SYMBOL.""" + + root_cvs_directory = symbol.project.get_root_cvs_directory() + fill_source = FillSource(root_cvs_directory, symbol, {}) + + for cvs_symbol, svn_revision_range in range_map.items(): + fill_source._set_node(cvs_symbol.cvs_file, svn_revision_range) + + return fill_source + + |