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."""
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.
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
36 project -- (Project) the project containing this CVSPath.
38 parent_directory -- (CVSDirectory or None) the CVSDirectory
39 containing this CVSPath.
41 rcs_basename -- (string) the base name of the filename path in the
42 CVS repository corresponding to this CVSPath (but with ',v'
43 removed for CVSFiles). The rcs_basename of the root directory
46 rcs_path -- (string) the filesystem path to this CVSPath in the
47 CVS repository. This is in native format, and already
48 normalised the way os.path.normpath() normalises paths. It
49 starts with the repository path passed to
50 run_options.add_project() in the options.py file.
52 ordinal -- (int) the order that this instance should be sorted
53 relative to other CVSPath instances. This member is set based
54 on the ordering imposed by sort_key() by CVSPathDatabase after
55 all CVSFiles have been processed. Comparisons of CVSPath
56 using __cmp__() simply compare the ordinals.
69 def __init__(self
, id, project
, parent_directory
, rcs_basename
):
71 self
.project
= project
72 self
.parent_directory
= parent_directory
73 self
.rcs_basename
= rcs_basename
75 # The rcs_path used to be computed on demand, but it turned out to
76 # be a hot path through the code in some cases. It's used by
77 # SubtreeSymbolTransform and similar transforms, so it's called at
80 # (num_files * num_symbols_per_file * num_subtree_symbol_transforms)
82 # times. On a large repository with several subtree symbol
83 # transforms, that can exceed 100,000,000 calls. And
84 # _calculate_rcs_path() is quite complex, so doing that every time
85 # could add about 10 minutes to the cvs2svn runtime.
87 # So now we precalculate this and just return it.
88 self
.rcs_path
= os
.path
.normpath(self
._calculate
_rcs
_path
())
90 def __getstate__(self
):
91 """This method must only be called after ordinal has been set."""
94 self
.id, self
.project
.id,
95 self
.parent_directory
, self
.rcs_basename
,
99 def __setstate__(self
, state
):
102 self
.parent_directory
, self
.rcs_basename
,
105 self
.project
= Ctx()._projects
[project_id
]
106 self
.rcs_path
= os
.path
.normpath(self
._calculate
_rcs
_path
())
108 def get_ancestry(self
):
109 """Return a list of the CVSPaths leading from the root path to SELF.
111 Return the CVSPaths in a list, starting with
112 self.project.get_root_cvs_directory() and ending with self."""
118 p
= p
.parent_directory
123 def get_cvs_path(self
):
124 """Return the canonical path within the Project.
128 - Uses forward slashes
130 - Doesn't include ',v' for files
132 - This doesn't include the 'Attic' segment of the path unless the
133 file is to be left in an Attic directory in the SVN repository;
134 i.e., if a filename exists in and out of Attic and the
135 --retain-conflicting-attic-files option was specified.
139 return path_join(*[p
.rcs_basename
for p
in self
.get_ancestry()[1:]])
141 cvs_path
= property(get_cvs_path
)
143 def _get_dir_components(self
):
144 """Return a list containing the components of the path leading to SELF.
146 The return value contains the base names of all of the parent
147 directories (except for the root directory) and SELF."""
149 return [p
.rcs_basename
for p
in self
.get_ancestry()[1:]]
152 """Compare two CVSPath instances for equality.
154 This method is supplied to avoid using __cmp__() for comparing for
160 """Return the key that should be used for sorting CVSPath instances.
162 This is a relatively expensive computation, so it is only used
163 once, the the results are used to set the ordinal member."""
166 # Sort first by project:
168 # Then by directory components:
169 self
._get
_dir
_components
(),
173 """This method must only be called after ordinal has been set."""
175 return cmp(a
.ordinal
, b
.ordinal
)
178 class CVSDirectory(CVSPath
):
179 """Represent a CVS directory.
183 id -- (int or None) unique id for this file. If None, a new id is
186 project -- (Project) the project containing this file.
188 parent_directory -- (CVSDirectory or None) the CVSDirectory
189 containing this CVSDirectory.
191 rcs_basename -- (string) the base name of the filename path in the
192 CVS repository corresponding to this CVSDirectory. The
193 rcs_basename of the root directory of a project is ''.
195 ordinal -- (int) the order that this instance should be sorted
196 relative to other CVSPath instances. See CVSPath.ordinal.
198 empty_subdirectory_ids -- (list of int) a list of the ids of any
199 direct subdirectories that are empty. (An empty directory is
200 defined to be a directory that doesn't contain any RCS files
201 or non-empty subdirectories.
205 __slots__
= ['empty_subdirectory_ids']
207 def __init__(self
, id, project
, parent_directory
, rcs_basename
):
208 """Initialize a new CVSDirectory object."""
210 CVSPath
.__init
__(self
, id, project
, parent_directory
, rcs_basename
)
212 # This member is filled in by CollectData.close():
213 self
.empty_subdirectory_ids
= []
215 def _calculate_rcs_path(self
):
216 """Return the filesystem path in the CVS repo corresponding to SELF."""
218 if self
.parent_directory
is None:
219 return self
.project
.project_cvs_repos_path
222 self
.parent_directory
.rcs_path
, self
.rcs_basename
225 def __getstate__(self
):
227 CVSPath
.__getstate
__(self
),
228 self
.empty_subdirectory_ids
,
231 def __setstate__(self
, state
):
234 self
.empty_subdirectory_ids
,
236 CVSPath
.__setstate
__(self
, cvs_path_state
)
239 """For convenience only. The format is subject to change at any time."""
241 return self
.cvs_path
+ '/'
244 return 'CVSDirectory<%x>(%r)' % (self
.id, str(self
),)
247 class CVSFile(CVSPath
):
248 """Represent a CVS file.
252 id -- (int) unique id for this file.
254 project -- (Project) the project containing this file.
256 parent_directory -- (CVSDirectory) the CVSDirectory containing
259 rcs_basename -- (string) the base name of the RCS file in the CVS
260 repository corresponding to this CVSPath (but with the ',v'
263 ordinal -- (int) the order that this instance should be sorted
264 relative to other CVSPath instances. See CVSPath.ordinal.
266 _in_attic -- (bool) True if RCS file is in an Attic subdirectory
267 that is not considered the parent directory. (If a file is
268 in-and-out-of-attic and one copy is to be left in Attic after
269 the conversion, then the Attic directory is that file's
270 PARENT_DIRECTORY and _IN_ATTIC is False.)
272 executable -- (bool) True iff RCS file has executable bit set.
274 file_size -- (long) size of the RCS file in bytes.
276 mode -- (string or None) 'kv', 'b', etc., as read from the CVS
279 description -- (string or None) the file description as read from
282 properties -- (dict) file properties that are preserved across
283 this history of this file. Keys are strings; values are
284 strings (indicating the property value) or None (indicating
285 that the property should be left unset). These properties can
286 be overridden by CVSRevision.properties. Different backends
287 can use these properties for different purposes; for cvs2svn
288 they become SVN versioned properties. Properties whose names
289 start with underscore are reserved for internal cvs2svn
292 PARENT_DIRECTORY might contain an 'Attic' component if it should be
293 retained in the SVN repository; i.e., if the same filename exists
294 out of Attic and the --retain-conflicting-attic-files option was
309 self
, id, project
, parent_directory
, rcs_basename
, in_attic
,
310 executable
, file_size
, mode
, description
312 """Initialize a new CVSFile object."""
314 assert parent_directory
is not None
316 # This member is needed by _calculate_rcs_path(), which is
317 # called by CVSPath.__init__(). So initialize it before calling
318 # CVSPath.__init__().
319 self
._in
_attic
= in_attic
320 CVSPath
.__init
__(self
, id, project
, parent_directory
, rcs_basename
)
322 self
.executable
= executable
323 self
.file_size
= file_size
325 self
.description
= description
326 self
.properties
= None
328 def determine_file_properties(self
, file_property_setters
):
329 """Determine the properties for this file from FILE_PROPERTY_SETTERS.
331 This must only be called after SELF.mode and SELF.description have
332 been set by CollectData."""
336 for file_property_setter
in file_property_setters
:
337 file_property_setter
.set_properties(self
)
339 def _calculate_rcs_path(self
):
340 """Return the filesystem path to the RCS file for this CVSFile."""
344 self
.parent_directory
.rcs_path
, 'Attic', self
.rcs_basename
+ ',v'
348 self
.parent_directory
.rcs_path
, self
.rcs_basename
+ ',v'
351 def __getstate__(self
):
353 CVSPath
.__getstate
__(self
),
354 self
._in
_attic
, self
.executable
, self
.file_size
, self
.mode
,
355 self
.description
, self
.properties
,
358 def __setstate__(self
, state
):
361 self
._in
_attic
, self
.executable
, self
.file_size
, self
.mode
,
362 self
.description
, self
.properties
,
364 CVSPath
.__setstate
__(self
, cvs_path_state
)
367 """For convenience only. The format is subject to change at any time."""
372 return 'CVSFile<%x>(%r)' % (self
.id, str(self
),)