Add option for excluding paths from conversion
[cvs2svn.git] / cvs2svn_lib / openings_closings.py
blobb1d4093f61f242a0b8e93422283fba7c528f6980
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 to keep track of symbol openings/closings."""
20 import cPickle
22 from cvs2svn_lib import config
23 from cvs2svn_lib.common import InternalError
24 from cvs2svn_lib.artifact_manager import artifact_manager
25 from cvs2svn_lib.svn_revision_range import SVNRevisionRange
28 # Constants used in SYMBOL_OPENINGS_CLOSINGS
29 OPENING = 'O'
30 CLOSING = 'C'
33 class SymbolingsLogger:
34 """Manage the file that contains lines for symbol openings and closings.
36 This data will later be used to determine valid SVNRevision ranges
37 from which a file can be copied when creating a branch or tag in
38 Subversion. Do this by finding 'Openings' and 'Closings' for each
39 file copied onto a branch or tag.
41 An 'Opening' is the beginning of the lifetime of the source
42 (CVSRevision or CVSBranch) from which a given CVSSymbol sprouts.
44 The 'Closing' is the SVN revision when the source is deleted or
45 overwritten.
47 For example, on file 'foo.c', branch BEE has branch number 1.2.2 and
48 obviously sprouts from revision 1.2. Therefore, the SVN revision
49 when 1.2 is committed is the opening for BEE on path 'foo.c', and
50 the SVN revision when 1.3 is committed is the closing for BEE on
51 path 'foo.c'. Note that there may be many revisions chronologically
52 between 1.2 and 1.3, for example, revisions on branches of 'foo.c',
53 perhaps even including on branch BEE itself. But 1.3 is the next
54 revision *on the same line* as 1.2, that is why it is the closing
55 revision for those symbolic names of which 1.2 is the opening.
57 The reason for doing all this hullabaloo is (1) to determine what
58 range of SVN revision numbers can be used as the source of a copy of
59 a particular file onto a branch/tag, and (2) to minimize the number
60 of copies and deletes per creation by choosing source SVN revision
61 numbers that can be used for as many files as possible.
63 For example, revisions 1.2 and 1.3 of foo.c might correspond to
64 revisions 17 and 30 in Subversion. That means that when creating
65 branch BEE, foo.c has to be copied from a Subversion revision number
66 in the range 17 <= revnum < 30. Now if there were another file,
67 'bar.c', in the same directory, and 'bar.c's opening and closing for
68 BEE correspond to revisions 24 and 39 in Subversion, then we can
69 kill two birds with one stone by copying the whole directory from
70 somewhere in the range 24 <= revnum < 30."""
72 def __init__(self):
73 self.symbolings = open(
74 artifact_manager.get_temp_file(config.SYMBOL_OPENINGS_CLOSINGS), 'w')
76 def log_revision(self, cvs_rev, svn_revnum):
77 """Log any openings and closings found in CVS_REV."""
79 for (symbol_id, cvs_symbol_id,) in cvs_rev.opened_symbols:
80 self._log_opening(symbol_id, cvs_symbol_id, svn_revnum)
82 for (symbol_id, cvs_symbol_id) in cvs_rev.closed_symbols:
83 self._log_closing(symbol_id, cvs_symbol_id, svn_revnum)
85 def log_branch_revision(self, cvs_branch, svn_revnum):
86 """Log any openings and closings found in CVS_BRANCH."""
88 for (symbol_id, cvs_symbol_id,) in cvs_branch.opened_symbols:
89 self._log_opening(symbol_id, cvs_symbol_id, svn_revnum)
91 def _log(self, symbol_id, cvs_symbol_id, svn_revnum, type):
92 """Log an opening or closing to self.symbolings.
94 Write out a single line to the symbol_openings_closings file
95 representing that SVN_REVNUM is either the opening or closing
96 (TYPE) of CVS_SYMBOL_ID for SYMBOL_ID.
98 TYPE should be one of the following constants: OPENING or CLOSING."""
100 self.symbolings.write(
101 '%x %d %s %x\n' % (symbol_id, svn_revnum, type, cvs_symbol_id)
104 def _log_opening(self, symbol_id, cvs_symbol_id, svn_revnum):
105 """Log an opening to self.symbolings.
107 See _log() for more information."""
109 self._log(symbol_id, cvs_symbol_id, svn_revnum, OPENING)
111 def _log_closing(self, symbol_id, cvs_symbol_id, svn_revnum):
112 """Log a closing to self.symbolings.
114 See _log() for more information."""
116 self._log(symbol_id, cvs_symbol_id, svn_revnum, CLOSING)
118 def close(self):
119 self.symbolings.close()
120 self.symbolings = None
123 class SymbolingsReader:
124 """Provides an interface to retrieve symbol openings and closings.
126 This class accesses the SYMBOL_OPENINGS_CLOSINGS_SORTED file and the
127 SYMBOL_OFFSETS_DB. Does the heavy lifting of finding and returning
128 the correct opening and closing Subversion revision numbers for a
129 given symbolic name and SVN revision number range."""
131 def __init__(self):
132 """Opens the SYMBOL_OPENINGS_CLOSINGS_SORTED for reading, and
133 reads the offsets database into memory."""
135 self.symbolings = open(
136 artifact_manager.get_temp_file(
137 config.SYMBOL_OPENINGS_CLOSINGS_SORTED),
138 'r')
139 # The offsets_db is really small, and we need to read and write
140 # from it a fair bit, so suck it into memory
141 offsets_db = file(
142 artifact_manager.get_temp_file(config.SYMBOL_OFFSETS_DB), 'rb')
143 # A map from symbol_id to offset. The values of this map are
144 # incremented as the openings and closings for a symbol are
145 # consumed.
146 self.offsets = cPickle.load(offsets_db)
147 offsets_db.close()
149 def close(self):
150 self.symbolings.close()
151 del self.symbolings
152 del self.offsets
154 def _generate_lines(self, symbol):
155 """Generate the lines for SYMBOL.
157 SYMBOL is a TypedSymbol instance. Yield the tuple (revnum, type,
158 cvs_symbol_id) for all openings and closings for SYMBOL."""
160 if symbol.id in self.offsets:
161 # Set our read offset for self.symbolings to the offset for this
162 # symbol:
163 self.symbolings.seek(self.offsets[symbol.id])
165 while True:
166 line = self.symbolings.readline().rstrip()
167 if not line:
168 break
169 (id, revnum, type, cvs_symbol_id) = line.split()
170 id = int(id, 16)
171 revnum = int(revnum)
172 if id != symbol.id:
173 break
174 cvs_symbol_id = int(cvs_symbol_id, 16)
176 yield (revnum, type, cvs_symbol_id)
178 def get_range_map(self, svn_symbol_commit):
179 """Return the ranges of all CVSSymbols in SVN_SYMBOL_COMMIT.
181 Return a map { CVSSymbol : SVNRevisionRange }."""
183 # A map { cvs_symbol_id : CVSSymbol }:
184 cvs_symbol_map = {}
185 for cvs_symbol in svn_symbol_commit.get_cvs_items():
186 cvs_symbol_map[cvs_symbol.id] = cvs_symbol
188 range_map = {}
190 for (revnum, type, cvs_symbol_id) \
191 in self._generate_lines(svn_symbol_commit.symbol):
192 cvs_symbol = cvs_symbol_map.get(cvs_symbol_id)
193 if cvs_symbol is None:
194 # This CVSSymbol is not part of SVN_SYMBOL_COMMIT.
195 continue
196 range = range_map.get(cvs_symbol)
197 if type == OPENING:
198 if range is not None:
199 raise InternalError(
200 'Multiple openings logged for %r' % (cvs_symbol,)
202 range_map[cvs_symbol] = SVNRevisionRange(
203 cvs_symbol.source_lod, revnum
205 else:
206 if range is None:
207 raise InternalError(
208 'Closing precedes opening for %r' % (cvs_symbol,)
210 if range.closing_revnum is not None:
211 raise InternalError(
212 'Multiple closings logged for %r' % (cvs_symbol,)
214 range.add_closing(revnum)
216 # Make sure that all CVSSymbols are accounted for, and adjust the
217 # closings to be not later than svn_symbol_commit.revnum.
218 for cvs_symbol in cvs_symbol_map.itervalues():
219 try:
220 range = range_map[cvs_symbol]
221 except KeyError:
222 raise InternalError('No opening for %s' % (cvs_symbol,))
224 if range.opening_revnum >= svn_symbol_commit.revnum:
225 raise InternalError(
226 'Opening in r%d not ready for %s in r%d'
227 % (range.opening_revnum, cvs_symbol, svn_symbol_commit.revnum,)
230 if range.closing_revnum is not None \
231 and range.closing_revnum > svn_symbol_commit.revnum:
232 range.closing_revnum = None
234 return range_map