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 the SVNRevisionRange class."""
22 from cvs2svn_lib
.common
import SVN_INVALID_REVNUM
25 class SVNRevisionRange
:
26 """The range of subversion revision numbers from which a path can be
27 copied. self.opening_revnum is the number of the earliest such
28 revision, and self.closing_revnum is one higher than the number of
29 the last such revision. If self.closing_revnum is None, then no
30 closings were registered."""
32 def __init__(self
, source_lod
, opening_revnum
):
33 self
.source_lod
= source_lod
34 self
.opening_revnum
= opening_revnum
35 self
.closing_revnum
= None
37 def add_closing(self
, closing_revnum
):
38 # When we have a non-trunk default branch, we may have multiple
39 # closings--only register the first closing we encounter.
40 if self
.closing_revnum
is None:
41 self
.closing_revnum
= closing_revnum
43 def __contains__(self
, revnum
):
44 """Return True iff REVNUM is contained in the range."""
47 self
.opening_revnum
<= revnum \
48 and (self
.closing_revnum
is None or revnum
< self
.closing_revnum
)
52 if self
.closing_revnum
is None:
53 return '[%d:]' % (self
.opening_revnum
,)
55 return '[%d:%d]' % (self
.opening_revnum
, self
.closing_revnum
,)
62 """Represent the scores for a range of revisions."""
64 def __init__(self
, svn_revision_ranges
):
65 """Initialize based on SVN_REVISION_RANGES.
67 SVN_REVISION_RANGES is a list of SVNRevisionRange objects.
69 The score of an svn source is defined to be the number of
70 SVNRevisionRanges on that LOD that include the revision. A score
71 thus indicates that copying the corresponding revision (or any
72 following revision up to the next revision in the list) of the
73 object in question would yield that many correct paths at or
74 underneath the object. There may be other paths underneath it
75 that are not correct and would need to be deleted or recopied;
76 those can only be detected by descending and examining their
79 If SVN_REVISION_RANGES is empty, then all scores are undefined."""
83 for range in svn_revision_ranges
:
84 source_lod
= range.source_lod
86 deltas
= deltas_map
[source_lod
]
89 deltas_map
[source_lod
] = deltas
90 deltas
.append((range.opening_revnum
, +1))
91 if range.closing_revnum
is not None:
92 deltas
.append((range.closing_revnum
, -1))
96 # {SOURCE_LOD : [(REV1 SCORE1), (REV2 SCORE2), (REV3 SCORE3), ...]}
98 # where the tuples are sorted by revision number and the revision
99 # numbers are distinct. Score is the number of correct paths that
100 # would result from using the specified SOURCE_LOD and revision
101 # number (or any other revision preceding the next revision
102 # listed) as a source. For example, the score of any revision REV
103 # in the range REV2 <= REV < REV3 is equal to SCORE2.
104 self
._scores
_map
= {}
106 for (source_lod
,deltas
) in deltas_map
.items():
107 # Sort by revision number:
110 # Initialize output list with zeroth element of deltas. This
111 # element must exist, because it was verified that
112 # svn_revision_ranges (and therefore openings) is not empty.
113 scores
= [ deltas
[0] ]
115 for (rev
, change
) in deltas
[1:]:
117 if rev
== scores
[-1][0]:
118 # Same revision as last entry; modify last entry:
119 scores
[-1] = (rev
, total
)
121 # Previously-unseen revision; create new entry:
122 scores
.append((rev
, total
))
123 self
._scores
_map
[source_lod
] = scores
125 def get_score(self
, range):
126 """Return the score for RANGE's opening revision.
128 If RANGE doesn't appear explicitly in self.scores, use the score
129 of the higest revision preceding RANGE. If there are no preceding
130 revisions, then the score for RANGE is unknown; in this case,
134 scores
= self
._scores
_map
[range.source_lod
]
138 # Remember, according to the tuple sorting rules,
140 # (revnum, anything,) < (revnum+1,) < (revnum+1, anything,)
141 predecessor_index
= bisect
.bisect_right(
142 scores
, (range.opening_revnum
+ 1,)
145 if predecessor_index
< 0:
148 return scores
[predecessor_index
][1]
150 def get_best_revnum(self
):
151 """Find the revnum with the highest score.
153 Return (revnum, score) for the revnum with the highest score. If
154 the highest score is shared by multiple revisions, select the
157 best_source_lod
= None
158 best_revnum
= SVN_INVALID_REVNUM
161 source_lods
= self
._scores
_map
.keys()
163 for source_lod
in source_lods
:
164 for revnum
, score
in self
._scores
_map
[source_lod
]:
165 if score
> best_score
:
166 best_source_lod
= source_lod
169 return best_source_lod
, best_revnum
, best_score