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 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.
61 def __init__(self
, id, project
, parent_directory
, basename
):
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."""
73 self
.id, self
.project
.id,
74 self
.parent_directory
, self
.basename
,
78 def __setstate__(self
, state
):
81 self
.parent_directory
, self
.basename
,
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
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
105 # So now we precalculate this and just return it.
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."""
119 p
= p
.parent_directory
124 def get_cvs_path(self
):
125 """Return the canonical path within the Project.
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:]]
153 """Compare two CVSPath instances for equality.
155 This method is supplied to avoid using __cmp__() for comparing for
160 def slow_compare(a
, b
):
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
())
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.
179 id -- (int or None) unique id for this file. If None, a new id is
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
216 self
.parent_directory
.filename
, self
.basename
219 def __getstate__(self
):
221 CVSPath
.__getstate
__(self
),
222 self
.empty_subdirectory_ids
,
225 def __setstate__(self
, state
):
228 self
.empty_subdirectory_ids
,
230 CVSPath
.__setstate
__(self
, cvs_path_state
)
233 """For convenience only. The format is subject to change at any time."""
235 return self
.cvs_path
+ '/'
238 return 'CVSDirectory<%x>(%r)' % (self
.id, str(self
),)
241 class CVSFile(CVSPath
):
242 """Represent a CVS file.
246 id -- (int) unique id for this file.
248 project -- (Project) the project containing this file.
250 parent_directory -- (CVSDirectory) the CVSDirectory containing
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
271 description -- (string or None) the file description as read from
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
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.
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
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."""
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."""
335 self
.parent_directory
.filename
, 'Attic', self
.basename
+ ',v'
339 self
.parent_directory
.filename
, self
.basename
+ ',v'
342 def __getstate__(self
):
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
):
352 self
._in
_attic
, self
.executable
, self
.file_size
, self
.mode
,
353 self
.description
, self
.properties
,
355 CVSPath
.__setstate
__(self
, cvs_path_state
)
358 """For convenience only. The format is subject to change at any time."""
363 return 'CVSFile<%x>(%r)' % (self
.id, str(self
),)