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 # This member is filled in by CollectData.close():
205 self
.empty_subdirectory_ids
= []
207 CVSPath
.__init
__(self
, id, project
, parent_directory
, basename
)
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.
270 PARENT_DIRECTORY might contain an 'Attic' component if it should be
271 retained in the SVN repository; i.e., if the same filename exists out
272 of Attic and the --retain-conflicting-attic-files option was specified.
285 self
, id, project
, parent_directory
, basename
, in_attic
,
286 executable
, file_size
, mode
, description
288 """Initialize a new CVSFile object."""
290 assert parent_directory
is not None
292 self
._in
_attic
= in_attic
293 self
.executable
= executable
294 self
.file_size
= file_size
296 self
.description
= description
297 CVSPath
.__init
__(self
, id, project
, parent_directory
, basename
)
299 def _calculate_filename(self
):
300 """Return the filesystem path to this CVSPath in the CVS repository."""
304 self
.parent_directory
.filename
, 'Attic', self
.basename
+ ',v'
308 self
.parent_directory
.filename
, self
.basename
+ ',v'
311 def __getstate__(self
):
313 CVSPath
.__getstate
__(self
),
314 self
._in
_attic
, self
.executable
, self
.file_size
, self
.mode
,
318 def __setstate__(self
, state
):
321 self
._in
_attic
, self
.executable
, self
.file_size
, self
.mode
,
324 CVSPath
.__setstate
__(self
, cvs_path_state
)
327 """For convenience only. The format is subject to change at any time."""
332 return 'CVSFile<%x>(%r)' % (self
.id, str(self
),)