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."""
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
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
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."""
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
)
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."""
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
),
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
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
146 self
.offsets
= cPickle
.load(offsets_db
)
150 self
.symbolings
.close()
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
163 self
.symbolings
.seek(self
.offsets
[symbol
.id])
166 line
= self
.symbolings
.readline().rstrip()
169 (id, revnum
, type, cvs_symbol_id
) = line
.split()
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 }:
185 for cvs_symbol
in svn_symbol_commit
.get_cvs_items():
186 cvs_symbol_map
[cvs_symbol
.id] = cvs_symbol
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.
196 range = range_map
.get(cvs_symbol
)
198 if range is not None:
200 'Multiple openings logged for %r' % (cvs_symbol
,)
202 range_map
[cvs_symbol
] = SVNRevisionRange(
203 cvs_symbol
.source_lod
, revnum
208 'Closing precedes opening for %r' % (cvs_symbol
,)
210 if range.closing_revnum
is not None:
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():
220 range = range_map
[cvs_symbol
]
222 raise InternalError('No opening for %s' % (cvs_symbol
,))
224 if range.opening_revnum
>= svn_symbol_commit
.revnum
:
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