File: fill_source.py

package info (click to toggle)
cvs2svn 2.4.0-4
  • links: PTS
  • area: main
  • in suites: stretch
  • size: 3,720 kB
  • sloc: python: 22,383; sh: 512; perl: 121; makefile: 84
file content (192 lines) | stat: -rw-r--r-- 6,643 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
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