cvs2git: Make the --blobfile argument optional.
[cvs2svn.git] / cvs2svn_lib / fill_source.py
blob2bb8e4c55088ca70362696fa7b8eaee8a50db206
1 # (Be in -*- python -*- mode.)
3 # ====================================================================
4 # Copyright (c) 2000-2008 CollabNet. All rights reserved.
6 # This software is licensed as described in the file COPYING, which
7 # you should have received as part of this distribution. The terms
8 # are also available at http://subversion.tigris.org/license-1.html.
9 # If newer versions of this license are posted there, you may use a
10 # newer version instead, at your option.
12 # This software consists of voluntary contributions made by many
13 # individuals. For exact contribution history, see the revision
14 # history and logs, available at http://cvs2svn.tigris.org/.
15 # ====================================================================
17 """This module contains classes describing the sources of symbol fills."""
20 from cvs2svn_lib.common import InternalError
21 from cvs2svn_lib.common import FatalError
22 from cvs2svn_lib.common import SVN_INVALID_REVNUM
23 from cvs2svn_lib.svn_revision_range import SVNRevisionRange
24 from cvs2svn_lib.svn_revision_range import RevisionScores
27 class FillSource:
28 """Representation of a fill source.
30 A FillSource keeps track of the paths that have to be filled in a
31 particular symbol fill.
33 This class holds a SVNRevisionRange instance for each CVSFile that
34 has to be filled within the subtree of the repository rooted at
35 self.cvs_path. The SVNRevisionRange objects are stored in a tree
36 in which the directory nodes are dictionaries mapping CVSPaths to
37 subnodes and the leaf nodes are the SVNRevisionRange objects telling
38 for what source_lod and what range of revisions the leaf could serve
39 as a source.
41 FillSource objects are able to compute the score for arbitrary
42 source LODs and source revision numbers.
44 These objects are used by the symbol filler in SVNOutputOption."""
46 def __init__(self, cvs_path, symbol, node_tree):
47 """Create a fill source.
49 The best LOD and SVN REVNUM to use as the copy source can be
50 determined by calling compute_best_source().
52 Members:
54 cvs_path -- (CVSPath): the CVSPath described by this FillSource.
56 _symbol -- (Symbol) the symbol to be filled.
58 _node_tree -- (dict) a tree stored as a map { CVSPath : node },
59 where subnodes have the same form. Leaves are
60 SVNRevisionRange instances telling the source_lod and range
61 of SVN revision numbers from which the CVSPath can be
62 copied.
64 """
66 self.cvs_path = cvs_path
67 self._symbol = symbol
68 self._node_tree = node_tree
70 def _set_node(self, cvs_file, svn_revision_range):
71 parent_node = self._get_node(cvs_file.parent_directory, create=True)
72 if cvs_file in parent_node:
73 raise InternalError(
74 '%s appeared twice in sources for %s' % (cvs_file, self._symbol)
76 parent_node[cvs_file] = svn_revision_range
78 def _get_node(self, cvs_path, create=False):
79 if cvs_path == self.cvs_path:
80 return self._node_tree
81 else:
82 parent_node = self._get_node(cvs_path.parent_directory, create=create)
83 try:
84 return parent_node[cvs_path]
85 except KeyError:
86 if create:
87 node = {}
88 parent_node[cvs_path] = node
89 return node
90 else:
91 raise
93 def compute_best_source(self, preferred_source):
94 """Determine the best source_lod and subversion revision number to copy.
96 Return the best source found, as an SVNRevisionRange instance. If
97 PREFERRED_SOURCE is not None and its opening is among the sources
98 with the best scores, return it; otherwise, return the oldest such
99 revision on the first such source_lod (ordered by the natural LOD
100 sort order). The return value's source_lod is the best LOD to
101 copy from, and its opening_revnum is the best SVN revision."""
103 # Aggregate openings and closings from our rev tree
104 svn_revision_ranges = self._get_revision_ranges(self._node_tree)
106 # Score the lists
107 revision_scores = RevisionScores(svn_revision_ranges)
109 best_source_lod, best_revnum, best_score = \
110 revision_scores.get_best_revnum()
112 if (
113 preferred_source is not None
114 and revision_scores.get_score(preferred_source) == best_score
116 best_source_lod = preferred_source.source_lod
117 best_revnum = preferred_source.opening_revnum
119 if best_revnum == SVN_INVALID_REVNUM:
120 raise FatalError(
121 "failed to find a revision to copy from when copying %s"
122 % self._symbol.name
125 return SVNRevisionRange(best_source_lod, best_revnum)
127 def _get_revision_ranges(self, node):
128 """Return a list of all the SVNRevisionRanges at and under NODE.
130 Include duplicates. This is a helper method used by
131 compute_best_source()."""
133 if isinstance(node, SVNRevisionRange):
134 # It is a leaf node.
135 return [ node ]
136 else:
137 # It is an intermediate node.
138 revision_ranges = []
139 for key, subnode in node.items():
140 revision_ranges.extend(self._get_revision_ranges(subnode))
141 return revision_ranges
143 def get_subsources(self):
144 """Generate (CVSPath, FillSource) for all direct subsources."""
146 if not isinstance(self._node_tree, SVNRevisionRange):
147 for cvs_path, node in self._node_tree.items():
148 fill_source = FillSource(cvs_path, self._symbol, node)
149 yield (cvs_path, fill_source)
151 def get_subsource_map(self):
152 """Return the map {CVSPath : FillSource} of direct subsources."""
154 src_entries = {}
156 for (cvs_path, fill_subsource) in self.get_subsources():
157 src_entries[cvs_path] = fill_subsource
159 return src_entries
161 def __str__(self):
162 """For convenience only. The format is subject to change at any time."""
164 return '%s(%s:%s)' % (
165 self.__class__.__name__, self._symbol, self.cvs_path,
168 def __repr__(self):
169 """For convenience only. The format is subject to change at any time."""
171 return '%s%r' % (self, self._node_tree,)
174 def get_source_set(symbol, range_map):
175 """Return a FillSource describing the fill sources for RANGE_MAP.
177 SYMBOL is either a Branch or a Tag. RANGE_MAP is a map { CVSSymbol
178 : SVNRevisionRange } as returned by
179 SymbolingsReader.get_range_map().
181 Use the SVNRevisionRanges from RANGE_MAP to create a FillSource
182 instance describing the sources for filling SYMBOL."""
184 root_cvs_directory = symbol.project.get_root_cvs_directory()
185 fill_source = FillSource(root_cvs_directory, symbol, {})
187 for cvs_symbol, svn_revision_range in range_map.items():
188 fill_source._set_node(cvs_symbol.cvs_file, svn_revision_range)
190 return fill_source