Split up too-long line.
[cvs2svn.git] / cvs2svn_lib / cvs_path.py
blob24494eecdb98ecad5c1dd06570ad2516fab64884
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 slow_compare() by CollectData 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 slow_compare(a, b):
161 return (
162 # Sort first by project:
163 cmp(a.project, b.project)
164 # Then by directory components:
165 or cmp(a._get_dir_components(), b._get_dir_components())
168 def __cmp__(a, b):
169 """This method must only be called after ordinal has been set."""
171 return cmp(a.ordinal, b.ordinal)
174 class CVSDirectory(CVSPath):
175 """Represent a CVS directory.
177 Members:
179 id -- (int or None) unique id for this file. If None, a new id is
180 generated.
182 project -- (Project) the project containing this file.
184 parent_directory -- (CVSDirectory or None) the CVSDirectory
185 containing this CVSDirectory.
187 basename -- (string) the base name of this CVSDirectory (no ',v').
189 ordinal -- (int) the order that this instance should be sorted
190 relative to other CVSPath instances. See CVSPath.ordinal.
192 empty_subdirectory_ids -- (list of int) a list of the ids of any
193 direct subdirectories that are empty. (An empty directory is
194 defined to be a directory that doesn't contain any RCS files
195 or non-empty subdirectories.
199 __slots__ = ['empty_subdirectory_ids']
201 def __init__(self, id, project, parent_directory, basename):
202 """Initialize a new CVSDirectory object."""
204 CVSPath.__init__(self, id, project, parent_directory, basename)
206 # This member is filled in by CollectData.close():
207 self.empty_subdirectory_ids = []
209 def _calculate_filename(self):
210 """Return the filesystem path to this CVSPath in the CVS repository."""
212 if self.parent_directory is None:
213 return self.project.project_cvs_repos_path
214 else:
215 return os.path.join(
216 self.parent_directory.filename, self.basename
219 def __getstate__(self):
220 return (
221 CVSPath.__getstate__(self),
222 self.empty_subdirectory_ids,
225 def __setstate__(self, state):
227 cvs_path_state,
228 self.empty_subdirectory_ids,
229 ) = state
230 CVSPath.__setstate__(self, cvs_path_state)
232 def __str__(self):
233 """For convenience only. The format is subject to change at any time."""
235 return self.cvs_path + '/'
237 def __repr__(self):
238 return 'CVSDirectory<%x>(%r)' % (self.id, str(self),)
241 class CVSFile(CVSPath):
242 """Represent a CVS file.
244 Members:
246 id -- (int) unique id for this file.
248 project -- (Project) the project containing this file.
250 parent_directory -- (CVSDirectory) the CVSDirectory containing
251 this CVSFile.
253 basename -- (string) the base name of this CVSFile (no ',v').
255 ordinal -- (int) the order that this instance should be sorted
256 relative to other CVSPath instances. See CVSPath.ordinal.
258 _in_attic -- (bool) True if RCS file is in an Attic subdirectory
259 that is not considered the parent directory. (If a file is
260 in-and-out-of-attic and one copy is to be left in Attic after
261 the conversion, then the Attic directory is that file's
262 PARENT_DIRECTORY and _IN_ATTIC is False.)
264 executable -- (bool) True iff RCS file has executable bit set.
266 file_size -- (long) size of the RCS file in bytes.
268 mode -- (string or None) 'kkv', 'kb', etc., as read from the CVS
269 file.
271 description -- (string or None) the file description as read from
272 the RCS file.
274 properties -- (dict) file properties that are preserved across
275 this history of this file. Keys are strings; values are
276 strings (indicating the property value) or None (indicating
277 that the property should be left unset). These properties can
278 be overridden by CVSRevision.properties. Different backends
279 can use these properties for different purposes; for cvs2svn
280 they become SVN versioned properties. Properties whose names
281 start with underscore are reserved for internal cvs2svn
282 purposes.
284 PARENT_DIRECTORY might contain an 'Attic' component if it should be
285 retained in the SVN repository; i.e., if the same filename exists out
286 of Attic and the --retain-conflicting-attic-files option was specified.
290 __slots__ = [
291 '_in_attic',
292 'executable',
293 'file_size',
294 'mode',
295 'description',
296 'properties',
299 def __init__(
300 self, id, project, parent_directory, basename, in_attic,
301 executable, file_size, mode, description
303 """Initialize a new CVSFile object."""
305 assert parent_directory is not None
307 # This member is needed by _calculate_filename(), which is called
308 # by CVSPath.__init__(). So initialize it before calling
309 # CVSPath.__init__().
310 self._in_attic = in_attic
311 CVSPath.__init__(self, id, project, parent_directory, basename)
313 self.executable = executable
314 self.file_size = file_size
315 self.mode = mode
316 self.description = description
317 self.properties = None
319 def determine_file_properties(self, file_property_setters):
320 """Determine the properties for this file from FILE_PROPERTY_SETTERS.
322 This must only be called after SELF.mode and SELF.description have
323 been set by CollectData."""
325 self.properties = {}
327 for file_property_setter in file_property_setters:
328 file_property_setter.set_properties(self)
330 def _calculate_filename(self):
331 """Return the filesystem path to this CVSPath in the CVS repository."""
333 if self._in_attic:
334 return os.path.join(
335 self.parent_directory.filename, 'Attic', self.basename + ',v'
337 else:
338 return os.path.join(
339 self.parent_directory.filename, self.basename + ',v'
342 def __getstate__(self):
343 return (
344 CVSPath.__getstate__(self),
345 self._in_attic, self.executable, self.file_size, self.mode,
346 self.description, self.properties,
349 def __setstate__(self, state):
351 cvs_path_state,
352 self._in_attic, self.executable, self.file_size, self.mode,
353 self.description, self.properties,
354 ) = state
355 CVSPath.__setstate__(self, cvs_path_state)
357 def __str__(self):
358 """For convenience only. The format is subject to change at any time."""
360 return self.cvs_path
362 def __repr__(self):
363 return 'CVSFile<%x>(%r)' % (self.id, str(self),)