Add an --include-empty-directories option.
[cvs2svn.git] / cvs2svn_lib / svn_run_options.py
blob86054a80829f2f110306ea589a2ead61132e2286
1 # (Be in -*- python -*- mode.)
3 # ====================================================================
4 # Copyright (c) 2000-2009 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 manages cvs2svn run options."""
20 import sys
21 import optparse
23 from cvs2svn_lib.version import VERSION
24 from cvs2svn_lib import config
25 from cvs2svn_lib.common import warning_prefix
26 from cvs2svn_lib.common import error_prefix
27 from cvs2svn_lib.common import FatalError
28 from cvs2svn_lib.common import normalize_svn_path
29 from cvs2svn_lib.log import Log
30 from cvs2svn_lib.context import Ctx
31 from cvs2svn_lib.run_options import not_both
32 from cvs2svn_lib.run_options import RunOptions
33 from cvs2svn_lib.run_options import ContextOption
34 from cvs2svn_lib.run_options import IncompatibleOption
35 from cvs2svn_lib.project import Project
36 from cvs2svn_lib.svn_output_option import DumpfileOutputOption
37 from cvs2svn_lib.svn_output_option import ExistingRepositoryOutputOption
38 from cvs2svn_lib.svn_output_option import NewRepositoryOutputOption
39 from cvs2svn_lib.symbol_strategy import TrunkPathRule
40 from cvs2svn_lib.symbol_strategy import BranchesPathRule
41 from cvs2svn_lib.symbol_strategy import TagsPathRule
44 class SVNRunOptions(RunOptions):
45 short_desc = 'convert a CVS repository into a Subversion repository'
47 synopsis = """\
48 .B cvs2svn
49 [\\fIOPTION\\fR]... \\fIOUTPUT-OPTION CVS-REPOS-PATH\\fR
50 .br
51 .B cvs2svn
52 [\\fIOPTION\\fR]... \\fI--options=PATH\\fR
53 """
55 long_desc = """\
56 Create a new Subversion repository based on the version history stored in a
57 CVS repository. Each CVS commit will be mirrored in the Subversion
58 repository, including such information as date of commit and id of the
59 committer.
61 \\fICVS-REPOS-PATH\\fR is the filesystem path of the part of the CVS
62 repository that you want to convert. It is not possible to convert a
63 CVS repository to which you only have remote access; see the FAQ for
64 more information. This path doesn't have to be the top level
65 directory of a CVS repository; it can point at a project within a
66 repository, in which case only that project will be converted. This
67 path or one of its parent directories has to contain a subdirectory
68 called CVSROOT (though the CVSROOT directory can be empty).
70 Multiple CVS repositories can be converted into a single Subversion
71 repository in a single run of cvs2svn, but only by using an
72 \\fB--options\\fR file.
73 """
75 files = """\
76 A directory called \\fIcvs2svn-tmp\\fR (or the directory specified by
77 \\fB--tmpdir\\fR) is used as scratch space for temporary data files.
78 """
80 see_also = [
81 ('cvs', '1'),
82 ('svn', '1'),
83 ('svnadmin', '1'),
86 def _get_output_options_group(self):
87 group = super(SVNRunOptions, self)._get_output_options_group()
89 group.add_option(IncompatibleOption(
90 '--svnrepos', '-s', type='string',
91 action='store',
92 help='path where SVN repos should be created',
93 man_help=(
94 'Write the output of the conversion into a Subversion repository '
95 'located at \\fIpath\\fR. This option causes a new Subversion '
96 'repository to be created at \\fIpath\\fR unless the '
97 '\\fB--existing-svnrepos\\fR option is also used.'
99 metavar='PATH',
101 self.parser.set_default('existing_svnrepos', False)
102 group.add_option(IncompatibleOption(
103 '--existing-svnrepos',
104 action='store_true',
105 help='load into existing SVN repository (for use with --svnrepos)',
106 man_help=(
107 'Load the converted CVS repository into an existing Subversion '
108 'repository, instead of creating a new repository. (This option '
109 'should be used in combination with '
110 '\\fB-s\\fR/\\fB--svnrepos\\fR.) The repository must either be '
111 'empty or contain no paths that overlap with those that will '
112 'result from the conversion. Please note that you need write '
113 'permission for the repository files.'
116 group.add_option(IncompatibleOption(
117 '--fs-type', type='string',
118 action='store',
119 help=(
120 'pass --fs-type=TYPE to "svnadmin create" (for use with '
121 '--svnrepos)'
123 man_help=(
124 'Pass \\fI--fs-type\\fR=\\fItype\\fR to "svnadmin create" when '
125 'creating a new repository.'
127 metavar='TYPE',
129 self.parser.set_default('bdb_txn_nosync', False)
130 group.add_option(IncompatibleOption(
131 '--bdb-txn-nosync',
132 action='store_true',
133 help=(
134 'pass --bdb-txn-nosync to "svnadmin create" (for use with '
135 '--svnrepos)'
137 man_help=(
138 'Pass \\fI--bdb-txn-nosync\\fR to "svnadmin create" when '
139 'creating a new BDB-style Subversion repository.'
142 self.parser.set_default('create_options', [])
143 group.add_option(IncompatibleOption(
144 '--create-option', type='string',
145 action='append', dest='create_options',
146 help='pass OPT to "svnadmin create" (for use with --svnrepos)',
147 man_help=(
148 'Pass \\fIopt\\fR to "svnadmin create" when creating a new '
149 'Subversion repository (can be specified multiple times to '
150 'pass multiple options).'
152 metavar='OPT',
154 group.add_option(IncompatibleOption(
155 '--dumpfile', type='string',
156 action='store',
157 help='just produce a dumpfile; don\'t commit to a repos',
158 man_help=(
159 'Just produce a dumpfile; don\'t commit to an SVN repository. '
160 'Write the dumpfile to \\fIpath\\fR.'
162 metavar='PATH',
165 group.add_option(ContextOption(
166 '--dry-run',
167 action='store_true',
168 help=(
169 'do not create a repository or a dumpfile; just print what '
170 'would happen.'
172 man_help=(
173 'Do not create a repository or a dumpfile; just print the '
174 'details of what cvs2svn would do if it were really converting '
175 'your repository.'
179 # Deprecated options:
180 self.parser.set_default('dump_only', False)
181 group.add_option(IncompatibleOption(
182 '--dump-only',
183 action='callback', callback=self.callback_dump_only,
184 help=optparse.SUPPRESS_HELP,
185 man_help=optparse.SUPPRESS_HELP,
187 group.add_option(IncompatibleOption(
188 '--create',
189 action='callback', callback=self.callback_create,
190 help=optparse.SUPPRESS_HELP,
191 man_help=optparse.SUPPRESS_HELP,
194 return group
196 def _get_conversion_options_group(self):
197 group = super(SVNRunOptions, self)._get_conversion_options_group()
199 self.parser.set_default('trunk_base', config.DEFAULT_TRUNK_BASE)
200 group.add_option(IncompatibleOption(
201 '--trunk', type='string',
202 action='store', dest='trunk_base',
203 help=(
204 'path for trunk (default: %s)'
205 % (config.DEFAULT_TRUNK_BASE,)
207 man_help=(
208 'Set the top-level path to use for trunk in the Subversion '
209 'repository. The default is \\fI%s\\fR.'
210 % (config.DEFAULT_TRUNK_BASE,)
212 metavar='PATH',
214 self.parser.set_default('branches_base', config.DEFAULT_BRANCHES_BASE)
215 group.add_option(IncompatibleOption(
216 '--branches', type='string',
217 action='store', dest='branches_base',
218 help=(
219 'path for branches (default: %s)'
220 % (config.DEFAULT_BRANCHES_BASE,)
222 man_help=(
223 'Set the top-level path to use for branches in the Subversion '
224 'repository. The default is \\fI%s\\fR.'
225 % (config.DEFAULT_BRANCHES_BASE,)
227 metavar='PATH',
229 self.parser.set_default('tags_base', config.DEFAULT_TAGS_BASE)
230 group.add_option(IncompatibleOption(
231 '--tags', type='string',
232 action='store', dest='tags_base',
233 help=(
234 'path for tags (default: %s)'
235 % (config.DEFAULT_TAGS_BASE,)
237 man_help=(
238 'Set the top-level path to use for tags in the Subversion '
239 'repository. The default is \\fI%s\\fR.'
240 % (config.DEFAULT_TAGS_BASE,)
242 metavar='PATH',
244 group.add_option(ContextOption(
245 '--include-empty-directories',
246 action='store_true', dest='include_empty_directories',
247 help=(
248 'include empty directories within the CVS repository '
249 'in the conversion'
251 man_help=(
252 'Treat empty subdirectories within the CVS repository as actual '
253 'directories, creating them when the parent directory is created '
254 'and removing them if and when the parent directory is pruned.'
257 group.add_option(ContextOption(
258 '--no-prune',
259 action='store_false', dest='prune',
260 help='don\'t prune empty directories',
261 man_help=(
262 'When all files are deleted from a directory in the Subversion '
263 'repository, don\'t delete the empty directory (the default is '
264 'to delete any empty directories).'
267 group.add_option(ContextOption(
268 '--no-cross-branch-commits',
269 action='store_false', dest='cross_branch_commits',
270 help='prevent the creation of cross-branch commits',
271 man_help=(
272 'Prevent the creation of commits that affect files on multiple '
273 'branches at once.'
277 return group
279 def _get_extraction_options_group(self):
280 group = super(SVNRunOptions, self)._get_extraction_options_group()
281 self._add_use_internal_co_option(group)
282 self._add_use_cvs_option(group)
283 self._add_use_rcs_option(group)
284 return group
286 def _get_environment_options_group(self):
287 group = super(SVNRunOptions, self)._get_environment_options_group()
289 group.add_option(ContextOption(
290 '--svnadmin', type='string',
291 action='store', dest='svnadmin_executable',
292 help='path to the "svnadmin" program',
293 man_help=(
294 'Path to the \\fIsvnadmin\\fR program. (\\fIsvnadmin\\fR is '
295 'needed when the \\fB-s\\fR/\\fB--svnrepos\\fR output option is '
296 'used.)'
298 metavar='PATH',
301 return group
303 def callback_dump_only(self, option, opt_str, value, parser):
304 parser.values.dump_only = True
305 Log().error(
306 warning_prefix +
307 ': The --dump-only option is deprecated (it is implied '
308 'by --dumpfile).\n'
311 def callback_create(self, option, opt_str, value, parser):
312 Log().error(
313 warning_prefix +
314 ': The behaviour produced by the --create option is now the '
315 'default;\n'
316 'passing the option is deprecated.\n'
319 def process_extraction_options(self):
320 """Process options related to extracting data from the CVS repository."""
321 self.process_all_extraction_options()
323 def process_output_options(self):
324 """Process the options related to SVN output."""
326 ctx = Ctx()
327 options = self.options
329 if options.dump_only and not options.dumpfile:
330 raise FatalError("'--dump-only' requires '--dumpfile' to be specified.")
332 if not options.svnrepos and not options.dumpfile and not ctx.dry_run:
333 raise FatalError("must pass one of '-s' or '--dumpfile'.")
335 not_both(options.svnrepos, '-s',
336 options.dumpfile, '--dumpfile')
338 not_both(options.dumpfile, '--dumpfile',
339 options.existing_svnrepos, '--existing-svnrepos')
341 not_both(options.bdb_txn_nosync, '--bdb-txn-nosync',
342 options.existing_svnrepos, '--existing-svnrepos')
344 not_both(options.dumpfile, '--dumpfile',
345 options.bdb_txn_nosync, '--bdb-txn-nosync')
347 not_both(options.fs_type, '--fs-type',
348 options.existing_svnrepos, '--existing-svnrepos')
350 if (
351 options.fs_type
352 and options.fs_type != 'bdb'
353 and options.bdb_txn_nosync
355 raise FatalError("cannot pass --bdb-txn-nosync with --fs-type=%s."
356 % options.fs_type)
358 if options.svnrepos:
359 if options.existing_svnrepos:
360 ctx.output_option = ExistingRepositoryOutputOption(options.svnrepos)
361 else:
362 ctx.output_option = NewRepositoryOutputOption(
363 options.svnrepos,
364 fs_type=options.fs_type, bdb_txn_nosync=options.bdb_txn_nosync,
365 create_options=options.create_options)
366 else:
367 ctx.output_option = DumpfileOutputOption(options.dumpfile)
369 def add_project(
370 self,
371 project_cvs_repos_path,
372 trunk_path=None, branches_path=None, tags_path=None,
373 initial_directories=[],
374 symbol_transforms=None,
375 symbol_strategy_rules=[],
377 """Add a project to be converted.
379 Most arguments are passed straight through to the Project
380 constructor. SYMBOL_STRATEGY_RULES is an iterable of
381 SymbolStrategyRules that will be applied to symbols in this
382 project."""
384 if trunk_path is not None:
385 trunk_path = normalize_svn_path(trunk_path, allow_empty=True)
386 if branches_path is not None:
387 branches_path = normalize_svn_path(branches_path, allow_empty=False)
388 if tags_path is not None:
389 tags_path = normalize_svn_path(tags_path, allow_empty=False)
391 initial_directories = [
392 path
393 for path in [trunk_path, branches_path, tags_path]
394 if path
395 ] + [
396 normalize_svn_path(path)
397 for path in initial_directories
400 symbol_strategy_rules = list(symbol_strategy_rules)
402 # Add rules to set the SVN paths for LODs depending on whether
403 # they are the trunk, tags, or branches:
404 if trunk_path is not None:
405 symbol_strategy_rules.append(TrunkPathRule(trunk_path))
406 if branches_path is not None:
407 symbol_strategy_rules.append(BranchesPathRule(branches_path))
408 if tags_path is not None:
409 symbol_strategy_rules.append(TagsPathRule(tags_path))
411 id = len(self.projects)
412 project = Project(
414 project_cvs_repos_path,
415 initial_directories=initial_directories,
416 symbol_transforms=symbol_transforms,
419 self.projects.append(project)
420 self.project_symbol_strategy_rules.append(symbol_strategy_rules)
422 def clear_projects(self):
423 """Clear the list of projects to be converted.
425 This method is for the convenience of options files, which may
426 want to import one another."""
428 del self.projects[:]
429 del self.project_symbol_strategy_rules[:]
431 def process_options(self):
432 # Consistency check for options and arguments.
433 if len(self.args) == 0:
434 self.usage()
435 sys.exit(1)
437 if len(self.args) > 1:
438 Log().error(error_prefix + ": must pass only one CVS repository.\n")
439 self.usage()
440 sys.exit(1)
442 cvsroot = self.args[0]
444 self.process_extraction_options()
445 self.process_output_options()
446 self.process_symbol_strategy_options()
447 self.process_property_setter_options()
449 # Create the default project (using ctx.trunk, ctx.branches, and
450 # ctx.tags):
451 self.add_project(
452 cvsroot,
453 trunk_path=self.options.trunk_base,
454 branches_path=self.options.branches_base,
455 tags_path=self.options.tags_base,
456 symbol_transforms=self.options.symbol_transforms,
457 symbol_strategy_rules=self.options.symbol_strategy_rules,