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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
|
# (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 to keep track of symbol openings/closings."""
import cPickle
from cvs2svn_lib import config
from cvs2svn_lib.common import InternalError
from cvs2svn_lib.artifact_manager import artifact_manager
from cvs2svn_lib.svn_revision_range import SVNRevisionRange
# Constants used in SYMBOL_OPENINGS_CLOSINGS
OPENING = 'O'
CLOSING = 'C'
class SymbolingsLogger:
"""Manage the file that contains lines for symbol openings and closings.
This data will later be used to determine valid SVNRevision ranges
from which a file can be copied when creating a branch or tag in
Subversion. Do this by finding 'Openings' and 'Closings' for each
file copied onto a branch or tag.
An 'Opening' is the beginning of the lifetime of the source
(CVSRevision or CVSBranch) from which a given CVSSymbol sprouts.
The 'Closing' is the SVN revision when the source is deleted or
overwritten.
For example, on file 'foo.c', branch BEE has branch number 1.2.2 and
obviously sprouts from revision 1.2. Therefore, the SVN revision
when 1.2 is committed is the opening for BEE on path 'foo.c', and
the SVN revision when 1.3 is committed is the closing for BEE on
path 'foo.c'. Note that there may be many revisions chronologically
between 1.2 and 1.3, for example, revisions on branches of 'foo.c',
perhaps even including on branch BEE itself. But 1.3 is the next
revision *on the same line* as 1.2, that is why it is the closing
revision for those symbolic names of which 1.2 is the opening.
The reason for doing all this hullabaloo is (1) to determine what
range of SVN revision numbers can be used as the source of a copy of
a particular file onto a branch/tag, and (2) to minimize the number
of copies and deletes per creation by choosing source SVN revision
numbers that can be used for as many files as possible.
For example, revisions 1.2 and 1.3 of foo.c might correspond to
revisions 17 and 30 in Subversion. That means that when creating
branch BEE, foo.c has to be copied from a Subversion revision number
in the range 17 <= revnum < 30. Now if there were another file,
'bar.c', in the same directory, and 'bar.c's opening and closing for
BEE correspond to revisions 24 and 39 in Subversion, then we can
kill two birds with one stone by copying the whole directory from
somewhere in the range 24 <= revnum < 30."""
def __init__(self):
self.symbolings = open(
artifact_manager.get_temp_file(config.SYMBOL_OPENINGS_CLOSINGS), 'w')
def log_revision(self, cvs_rev, svn_revnum):
"""Log any openings and closings found in CVS_REV."""
for (symbol_id, cvs_symbol_id,) in cvs_rev.opened_symbols:
self._log_opening(symbol_id, cvs_symbol_id, svn_revnum)
for (symbol_id, cvs_symbol_id) in cvs_rev.closed_symbols:
self._log_closing(symbol_id, cvs_symbol_id, svn_revnum)
def log_branch_revision(self, cvs_branch, svn_revnum):
"""Log any openings and closings found in CVS_BRANCH."""
for (symbol_id, cvs_symbol_id,) in cvs_branch.opened_symbols:
self._log_opening(symbol_id, cvs_symbol_id, svn_revnum)
def _log(self, symbol_id, cvs_symbol_id, svn_revnum, type):
"""Log an opening or closing to self.symbolings.
Write out a single line to the symbol_openings_closings file
representing that SVN_REVNUM is either the opening or closing
(TYPE) of CVS_SYMBOL_ID for SYMBOL_ID.
TYPE should be one of the following constants: OPENING or CLOSING."""
self.symbolings.write(
'%x %d %s %x\n' % (symbol_id, svn_revnum, type, cvs_symbol_id)
)
def _log_opening(self, symbol_id, cvs_symbol_id, svn_revnum):
"""Log an opening to self.symbolings.
See _log() for more information."""
self._log(symbol_id, cvs_symbol_id, svn_revnum, OPENING)
def _log_closing(self, symbol_id, cvs_symbol_id, svn_revnum):
"""Log a closing to self.symbolings.
See _log() for more information."""
self._log(symbol_id, cvs_symbol_id, svn_revnum, CLOSING)
def close(self):
self.symbolings.close()
self.symbolings = None
class SymbolingsReader:
"""Provides an interface to retrieve symbol openings and closings.
This class accesses the SYMBOL_OPENINGS_CLOSINGS_SORTED file and the
SYMBOL_OFFSETS_DB. Does the heavy lifting of finding and returning
the correct opening and closing Subversion revision numbers for a
given symbolic name and SVN revision number range."""
def __init__(self):
"""Opens the SYMBOL_OPENINGS_CLOSINGS_SORTED for reading, and
reads the offsets database into memory."""
self.symbolings = open(
artifact_manager.get_temp_file(
config.SYMBOL_OPENINGS_CLOSINGS_SORTED),
'r')
# The offsets_db is really small, and we need to read and write
# from it a fair bit, so suck it into memory
offsets_db = file(
artifact_manager.get_temp_file(config.SYMBOL_OFFSETS_DB), 'rb')
# A map from symbol_id to offset. The values of this map are
# incremented as the openings and closings for a symbol are
# consumed.
self.offsets = cPickle.load(offsets_db)
offsets_db.close()
def close(self):
self.symbolings.close()
del self.symbolings
del self.offsets
def _generate_lines(self, symbol):
"""Generate the lines for SYMBOL.
SYMBOL is a TypedSymbol instance. Yield the tuple (revnum, type,
cvs_symbol_id) for all openings and closings for SYMBOL."""
if symbol.id in self.offsets:
# Set our read offset for self.symbolings to the offset for this
# symbol:
self.symbolings.seek(self.offsets[symbol.id])
while True:
line = self.symbolings.readline().rstrip()
if not line:
break
(id, revnum, type, cvs_symbol_id) = line.split()
id = int(id, 16)
revnum = int(revnum)
if id != symbol.id:
break
cvs_symbol_id = int(cvs_symbol_id, 16)
yield (revnum, type, cvs_symbol_id)
def get_range_map(self, svn_symbol_commit):
"""Return the ranges of all CVSSymbols in SVN_SYMBOL_COMMIT.
Return a map { CVSSymbol : SVNRevisionRange }."""
# A map { cvs_symbol_id : CVSSymbol }:
cvs_symbol_map = {}
for cvs_symbol in svn_symbol_commit.get_cvs_items():
cvs_symbol_map[cvs_symbol.id] = cvs_symbol
range_map = {}
for (revnum, type, cvs_symbol_id) \
in self._generate_lines(svn_symbol_commit.symbol):
cvs_symbol = cvs_symbol_map.get(cvs_symbol_id)
if cvs_symbol is None:
# This CVSSymbol is not part of SVN_SYMBOL_COMMIT.
continue
range = range_map.get(cvs_symbol)
if type == OPENING:
if range is not None:
raise InternalError(
'Multiple openings logged for %r' % (cvs_symbol,)
)
range_map[cvs_symbol] = SVNRevisionRange(
cvs_symbol.source_lod, revnum
)
else:
if range is None:
raise InternalError(
'Closing precedes opening for %r' % (cvs_symbol,)
)
if range.closing_revnum is not None:
raise InternalError(
'Multiple closings logged for %r' % (cvs_symbol,)
)
range.add_closing(revnum)
# Make sure that all CVSSymbols are accounted for, and adjust the
# closings to be not later than svn_symbol_commit.revnum.
for cvs_symbol in cvs_symbol_map.itervalues():
try:
range = range_map[cvs_symbol]
except KeyError:
raise InternalError('No opening for %s' % (cvs_symbol,))
if range.opening_revnum >= svn_symbol_commit.revnum:
raise InternalError(
'Opening in r%d not ready for %s in r%d'
% (range.opening_revnum, cvs_symbol, svn_symbol_commit.revnum,)
)
if range.closing_revnum is not None \
and range.closing_revnum > svn_symbol_commit.revnum:
range.closing_revnum = None
return range_map
|