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 """This module contains database facilities used by cvs2svn."""
23 from cvs2svn_lib
.context
import Ctx
24 from cvs2svn_lib
.common
import FatalError
25 from cvs2svn_lib
.common
import IllegalSVNPathError
26 from cvs2svn_lib
.common
import normalize_svn_path
27 from cvs2svn_lib
.common
import verify_paths_disjoint
28 from cvs2svn_lib
.symbol_transform
import CompoundSymbolTransform
31 class FileInAndOutOfAtticException(Exception):
32 def __init__(self
, non_attic_path
, attic_path
):
35 "A CVS repository cannot contain both %s and %s"
36 % (non_attic_path
, attic_path
))
38 self
.non_attic_path
= non_attic_path
39 self
.attic_path
= attic_path
42 def normalize_ttb_path(opt
, path
, allow_empty
=False):
44 return normalize_svn_path(path
, allow_empty
)
45 except IllegalSVNPathError
, e
:
46 raise FatalError('Problem with %s: %s' % (opt
, e
,))
49 class Project(object):
50 """A project within a CVS repository."""
53 self
, id, project_cvs_repos_path
,
54 initial_directories
=[],
55 symbol_transforms
=None,
58 """Create a new Project record.
60 ID is a unique id for this project. PROJECT_CVS_REPOS_PATH is the
61 main CVS directory for this project (within the filesystem).
63 INITIAL_DIRECTORIES is an iterable of all SVN directories that
64 should be created when the project is first created. Normally,
65 this should include the trunk, branches, and tags directory.
67 SYMBOL_TRANSFORMS is an iterable of SymbolTransform instances
68 which will be used to transform any symbol names within this
71 EXCLUDE_PATHS is an iterable of paths that should be excluded from
72 the conversion. The paths should be relative to
73 PROJECT_CVS_REPOS_PATH and use slashes ('/'). Paths for
74 individual files should include the ',v' extension.
79 self
.project_cvs_repos_path
= os
.path
.normpath(project_cvs_repos_path
)
80 if not os
.path
.isdir(self
.project_cvs_repos_path
):
81 raise FatalError("The specified CVS repository path '%s' is not an "
82 "existing directory." % self
.project_cvs_repos_path
)
84 self
.cvs_repository_root
, self
.cvs_module
= \
85 self
.determine_repository_root(
86 os
.path
.abspath(self
.project_cvs_repos_path
))
88 # The SVN directories to add when the project is first created:
89 self
._initial
_directories
= []
91 for path
in initial_directories
:
93 path
= normalize_svn_path(path
, False)
94 except IllegalSVNPathError
, e
:
96 'Initial directory %r is not a legal SVN path: %s'
99 self
._initial
_directories
.append(path
)
101 verify_paths_disjoint(*self
._initial
_directories
)
103 # A list of transformation rules (regexp, replacement) applied to
104 # symbol names in this project.
105 if symbol_transforms
is None:
106 symbol_transforms
= []
108 self
.symbol_transform
= CompoundSymbolTransform(symbol_transforms
)
110 self
.exclude_paths
= set(exclude_paths
)
112 # The ID of the Trunk instance for this Project. This member is
113 # filled in during CollectRevsPass.
116 # The ID of the CVSDirectory representing the root directory of
117 # this project. This member is filled in during CollectRevsPass.
118 self
.root_cvs_directory_id
= None
120 def __eq__(self
, other
):
121 return self
.id == other
.id
123 def __cmp__(self
, other
):
124 return cmp(self
.cvs_module
, other
.cvs_module
) \
125 or cmp(self
.id, other
.id)
131 def determine_repository_root(path
):
132 """Ascend above the specified PATH if necessary to find the
133 cvs_repository_root (a directory containing a CVSROOT directory)
134 and the cvs_module (the path of the conversion root within the cvs
135 repository). Return the root path and the module path of this
136 project relative to the root.
138 NB: cvs_module must be seperated by '/', *not* by os.sep."""
140 def is_cvs_repository_root(path
):
141 return os
.path
.isdir(os
.path
.join(path
, 'CVSROOT'))
145 while not is_cvs_repository_root(path
):
146 # Step up one directory:
148 path
, module_component
= os
.path
.split(path
)
149 if path
== prev_path
:
150 # Hit the root (of the drive, on Windows) without finding a
153 "the path '%s' is not a CVS repository, nor a path "
154 "within a CVS repository. A CVS repository contains "
155 "a CVSROOT directory within its root directory."
158 cvs_module
= module_component
+ "/" + cvs_module
160 return path
, cvs_module
162 def transform_symbol(self
, cvs_file
, symbol_name
, revision
):
163 """Transform the symbol SYMBOL_NAME.
165 SYMBOL_NAME refers to revision number REVISION in CVS_FILE.
166 REVISION is the CVS revision number as a string, with zeros
167 removed (e.g., '1.7' or '1.7.2'). Use the renaming rules
168 specified with --symbol-transform to possibly rename the symbol.
169 Return the transformed symbol name, the original name if it should
170 not be transformed, or None if the symbol should be omitted from
173 return self
.symbol_transform
.transform(cvs_file
, symbol_name
, revision
)
176 """Return the Trunk instance for this project.
178 This method can only be called after self.trunk_id has been
179 initialized in CollectRevsPass."""
181 return Ctx()._symbol
_db
.get_symbol(self
.trunk_id
)
183 def get_root_cvs_directory(self
):
184 """Return the root CVSDirectory instance for this project.
186 This method can only be called after self.root_cvs_directory_id
187 has been initialized in CollectRevsPass."""
189 return Ctx()._cvs
_path
_db
.get_path(self
.root_cvs_directory_id
)
191 def get_initial_directories(self
):
192 """Generate the project's initial SVN directories.
194 Yield as strings the SVN paths of directories that should be
195 created when the project is first created."""
197 # Yield the path of the Trunk symbol for this project (which might
198 # differ from the one passed to the --trunk option because of
199 # SymbolStrategyRules). The trunk path might be '' during a
200 # trunk-only conversion, but that is OK because DumpstreamDelegate
201 # considers that directory to exist already and will therefore
203 yield self
.get_trunk().base_path
205 for path
in self
._initial
_directories
:
209 return self
.project_cvs_repos_path
212 def read_projects(filename
):
214 for project
in cPickle
.load(open(filename
, 'rb')):
215 retval
[project
.id] = project
219 def write_projects(filename
):
220 cPickle
.dump(Ctx()._projects
.values(), open(filename
, 'wb'), -1)