Use CVSFile.filename rather than CVSFile.get_filename().
[cvs2svn.git] / cvs2svn_lib / cvs_path.py
blobf92a454f9a6f4ee3a03378afb0d560fc30f59478
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 """Classes that represent files and directories within CVS repositories."""
19 import os
21 from cvs2svn_lib.common import path_join
22 from cvs2svn_lib.context import Ctx
25 class CVSPath(object):
26 """Represent a CVS file or directory.
28 Members:
30 id -- (int) unique ID for this CVSPath. At any moment, there is
31 at most one CVSPath instance with a particular ID. (This
32 means that object identity is the same as object equality, and
33 objects can be used as map keys even though they don't have a
34 __hash__() method).
36 project -- (Project) the project containing this CVSPath.
38 parent_directory -- (CVSDirectory or None) the CVSDirectory
39 containing this CVSPath.
41 basename -- (string) the base name of this CVSPath (no ',v'). The
42 basename of the root directory of a project is ''.
44 ordinal -- (int) the order that this instance should be sorted
45 relative to other CVSPath instances. This member is set based
46 on the ordering imposed by sort_key() by CVSPathDatabase after
47 all CVSFiles have been processed. Comparisons of CVSPath
48 using __cmp__() simply compare the ordinals.
50 """
52 __slots__ = [
53 'id',
54 'project',
55 'parent_directory',
56 'basename',
57 'ordinal',
58 'filename',
61 def __init__(self, id, project, parent_directory, basename):
62 self.id = id
63 self.project = project
64 self.parent_directory = parent_directory
65 self.basename = basename
67 self.filename = os.path.normpath(self._calculate_filename())
69 def __getstate__(self):
70 """This method must only be called after ordinal has been set."""
72 return (
73 self.id, self.project.id,
74 self.parent_directory, self.basename,
75 self.ordinal,
78 def __setstate__(self, state):
80 self.id, project_id,
81 self.parent_directory, self.basename,
82 self.ordinal,
83 ) = state
84 self.project = Ctx()._projects[project_id]
85 self.filename = os.path.normpath(self._calculate_filename())
87 def get_filename(self):
88 """Return the filesystem path to this CVSPath in the CVS repository.
90 This is in native format, and already normalised the way
91 os.path.normpath() normalises paths.
93 It starts with the repository path passed to run_options.add_project()
94 in the options.py file."""
96 # This turns out to be a hot path through the code.
97 # It's used by SubtreeSymbolTransform and similar transforms, so it's
98 # called at least:
99 # (num_files * num_symbols_per_file * num_subtree_symbol_transforms)
100 # times. On a large repository with several subtree symbol transforms,
101 # that can exceed 100,000,000 calls. And _calculate_filename() is quite
102 # complex, so doing that every time could add about 10 minutes to the
103 # cvs2svn runtime.
105 # So now we precalculate this and just return it.
107 return self.filename
109 def get_ancestry(self):
110 """Return a list of the CVSPaths leading from the root path to SELF.
112 Return the CVSPaths in a list, starting with
113 self.project.get_root_cvs_directory() and ending with self."""
115 ancestry = []
116 p = self
117 while p is not None:
118 ancestry.append(p)
119 p = p.parent_directory
121 ancestry.reverse()
122 return ancestry
124 def get_cvs_path(self):
125 """Return the canonical path within the Project.
127 The canonical path:
129 - Uses forward slashes
131 - Doesn't include ',v' for files
133 - This doesn't include the 'Attic' segment of the path unless the
134 file is to be left in an Attic directory in the SVN repository;
135 i.e., if a filename exists in and out of Attic and the
136 --retain-conflicting-attic-files option was specified.
140 return path_join(*[p.basename for p in self.get_ancestry()[1:]])
142 cvs_path = property(get_cvs_path)
144 def _get_dir_components(self):
145 """Return a list containing the components of the path leading to SELF.
147 The return value contains the base names of all of the parent
148 directories (except for the root directory) and SELF."""
150 return [p.basename for p in self.get_ancestry()[1:]]
152 def __eq__(a, b):
153 """Compare two CVSPath instances for equality.
155 This method is supplied to avoid using __cmp__() for comparing for
156 equality."""
158 return a is b
160 def sort_key(self):
161 """Return the key that should be used for sorting CVSPath instances.
163 This is a relatively expensive computation, so it is only used
164 once, the the results are used to set the ordinal member."""
166 return (
167 # Sort first by project:
168 self.project,
169 # Then by directory components:
170 self._get_dir_components(),
173 def __cmp__(a, b):
174 """This method must only be called after ordinal has been set."""
176 return cmp(a.ordinal, b.ordinal)
179 class CVSDirectory(CVSPath):
180 """Represent a CVS directory.
182 Members:
184 id -- (int or None) unique id for this file. If None, a new id is
185 generated.
187 project -- (Project) the project containing this file.
189 parent_directory -- (CVSDirectory or None) the CVSDirectory
190 containing this CVSDirectory.
192 basename -- (string) the base name of this CVSDirectory (no ',v').
194 ordinal -- (int) the order that this instance should be sorted
195 relative to other CVSPath instances. See CVSPath.ordinal.
197 empty_subdirectory_ids -- (list of int) a list of the ids of any
198 direct subdirectories that are empty. (An empty directory is
199 defined to be a directory that doesn't contain any RCS files
200 or non-empty subdirectories.
204 __slots__ = ['empty_subdirectory_ids']
206 def __init__(self, id, project, parent_directory, basename):
207 """Initialize a new CVSDirectory object."""
209 CVSPath.__init__(self, id, project, parent_directory, basename)
211 # This member is filled in by CollectData.close():
212 self.empty_subdirectory_ids = []
214 def _calculate_filename(self):
215 """Return the filesystem path to this CVSPath in the CVS repository."""
217 if self.parent_directory is None:
218 return self.project.project_cvs_repos_path
219 else:
220 return os.path.join(
221 self.parent_directory.filename, self.basename
224 def __getstate__(self):
225 return (
226 CVSPath.__getstate__(self),
227 self.empty_subdirectory_ids,
230 def __setstate__(self, state):
232 cvs_path_state,
233 self.empty_subdirectory_ids,
234 ) = state
235 CVSPath.__setstate__(self, cvs_path_state)
237 def __str__(self):
238 """For convenience only. The format is subject to change at any time."""
240 return self.cvs_path + '/'
242 def __repr__(self):
243 return 'CVSDirectory<%x>(%r)' % (self.id, str(self),)
246 class CVSFile(CVSPath):
247 """Represent a CVS file.
249 Members:
251 id -- (int) unique id for this file.
253 project -- (Project) the project containing this file.
255 parent_directory -- (CVSDirectory) the CVSDirectory containing
256 this CVSFile.
258 basename -- (string) the base name of this CVSFile (no ',v').
260 ordinal -- (int) the order that this instance should be sorted
261 relative to other CVSPath instances. See CVSPath.ordinal.
263 _in_attic -- (bool) True if RCS file is in an Attic subdirectory
264 that is not considered the parent directory. (If a file is
265 in-and-out-of-attic and one copy is to be left in Attic after
266 the conversion, then the Attic directory is that file's
267 PARENT_DIRECTORY and _IN_ATTIC is False.)
269 executable -- (bool) True iff RCS file has executable bit set.
271 file_size -- (long) size of the RCS file in bytes.
273 mode -- (string or None) 'kkv', 'kb', etc., as read from the CVS
274 file.
276 description -- (string or None) the file description as read from
277 the RCS file.
279 properties -- (dict) file properties that are preserved across
280 this history of this file. Keys are strings; values are
281 strings (indicating the property value) or None (indicating
282 that the property should be left unset). These properties can
283 be overridden by CVSRevision.properties. Different backends
284 can use these properties for different purposes; for cvs2svn
285 they become SVN versioned properties. Properties whose names
286 start with underscore are reserved for internal cvs2svn
287 purposes.
289 PARENT_DIRECTORY might contain an 'Attic' component if it should be
290 retained in the SVN repository; i.e., if the same filename exists out
291 of Attic and the --retain-conflicting-attic-files option was specified.
295 __slots__ = [
296 '_in_attic',
297 'executable',
298 'file_size',
299 'mode',
300 'description',
301 'properties',
304 def __init__(
305 self, id, project, parent_directory, basename, in_attic,
306 executable, file_size, mode, description
308 """Initialize a new CVSFile object."""
310 assert parent_directory is not None
312 # This member is needed by _calculate_filename(), which is called
313 # by CVSPath.__init__(). So initialize it before calling
314 # CVSPath.__init__().
315 self._in_attic = in_attic
316 CVSPath.__init__(self, id, project, parent_directory, basename)
318 self.executable = executable
319 self.file_size = file_size
320 self.mode = mode
321 self.description = description
322 self.properties = None
324 def determine_file_properties(self, file_property_setters):
325 """Determine the properties for this file from FILE_PROPERTY_SETTERS.
327 This must only be called after SELF.mode and SELF.description have
328 been set by CollectData."""
330 self.properties = {}
332 for file_property_setter in file_property_setters:
333 file_property_setter.set_properties(self)
335 def _calculate_filename(self):
336 """Return the filesystem path to this CVSPath in the CVS repository."""
338 if self._in_attic:
339 return os.path.join(
340 self.parent_directory.filename, 'Attic', self.basename + ',v'
342 else:
343 return os.path.join(
344 self.parent_directory.filename, self.basename + ',v'
347 def __getstate__(self):
348 return (
349 CVSPath.__getstate__(self),
350 self._in_attic, self.executable, self.file_size, self.mode,
351 self.description, self.properties,
354 def __setstate__(self, state):
356 cvs_path_state,
357 self._in_attic, self.executable, self.file_size, self.mode,
358 self.description, self.properties,
359 ) = state
360 CVSPath.__setstate__(self, cvs_path_state)
362 def __str__(self):
363 """For convenience only. The format is subject to change at any time."""
365 return self.cvs_path
367 def __repr__(self):
368 return 'CVSFile<%x>(%r)' % (self.id, str(self),)