Use new method FilePropertySetter.maybe_set_property().
[cvs2svn.git] / cvs2svn_lib / svn_run_options.py
blobe7c6b84b1f3d0aec50898e924f9879473afa1624
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 import config
24 from cvs2svn_lib.common import warning_prefix
25 from cvs2svn_lib.common import error_prefix
26 from cvs2svn_lib.common import FatalError
27 from cvs2svn_lib.common import normalize_svn_path
28 from cvs2svn_lib.log import logger
29 from cvs2svn_lib.context import Ctx
30 from cvs2svn_lib.run_options import not_both
31 from cvs2svn_lib.run_options import RunOptions
32 from cvs2svn_lib.run_options import ContextOption
33 from cvs2svn_lib.run_options import IncompatibleOption
34 from cvs2svn_lib.project import Project
35 from cvs2svn_lib.svn_output_option import DumpfileOutputOption
36 from cvs2svn_lib.svn_output_option import ExistingRepositoryOutputOption
37 from cvs2svn_lib.svn_output_option import NewRepositoryOutputOption
38 from cvs2svn_lib.symbol_strategy import TrunkPathRule
39 from cvs2svn_lib.symbol_strategy import BranchesPathRule
40 from cvs2svn_lib.symbol_strategy import TagsPathRule
41 from cvs2svn_lib.property_setters import FilePropertySetter
44 class SVNEOLFixPropertySetter(FilePropertySetter):
45 """Set _eol_fix property.
47 This keyword is used to tell the RevisionReader how to munge EOLs
48 when generating the fulltext, based on how svn:eol-style is set."""
50 # A mapping from the value of the svn:eol-style property to the EOL
51 # string that should appear in a dumpfile:
52 EOL_REPLACEMENTS = {
53 'LF' : '\n',
54 'CR' : '\r',
55 'CRLF' : '\r\n',
56 'native' : '\n',
59 def set_properties(self, cvs_file):
60 # Fix EOLs if necessary:
61 eol_style = cvs_file.properties.get('svn:eol-style', None)
62 if eol_style:
63 self.maybe_set_property(
64 cvs_file, '_eol_fix', self.EOL_REPLACEMENTS[eol_style]
68 class SVNKeywordHandlingPropertySetter(FilePropertySetter):
69 """Set _keyword_handling=collapsed based on the file mode and svn:keywords.
71 This setting tells the RevisionReader that it has to collapse RCS
72 keywords when generating the fulltext."""
74 def set_properties(self, cvs_file):
75 if cvs_file.mode == 'b' or cvs_file.mode == 'o':
76 # Leave keywords in the form that they were checked in.
77 value = 'untouched'
78 elif cvs_file.mode == 'k':
79 # This mode causes CVS to collapse keywords on checkout, so we
80 # do the same:
81 value = 'collapsed'
82 elif cvs_file.properties.get('svn:keywords'):
83 # Subversion is going to expand the keywords, so they have to be
84 # collapsed in the dumpfile:
85 value = 'collapsed'
86 else:
87 # CVS expands keywords, so we will too.
88 value = 'expanded'
90 self.maybe_set_property(cvs_file, '_keyword_handling', value)
93 class SVNRunOptions(RunOptions):
94 short_desc = 'convert a CVS repository into a Subversion repository'
96 synopsis = """\
97 .B cvs2svn
98 [\\fIOPTION\\fR]... \\fIOUTPUT-OPTION CVS-REPOS-PATH\\fR
99 .br
100 .B cvs2svn
101 [\\fIOPTION\\fR]... \\fI--options=PATH\\fR
104 long_desc = """\
105 Create a new Subversion repository based on the version history stored in a
106 CVS repository. Each CVS commit will be mirrored in the Subversion
107 repository, including such information as date of commit and id of the
108 committer.
110 \\fICVS-REPOS-PATH\\fR is the filesystem path of the part of the CVS
111 repository that you want to convert. It is not possible to convert a
112 CVS repository to which you only have remote access; see the FAQ for
113 more information. This path doesn't have to be the top level
114 directory of a CVS repository; it can point at a project within a
115 repository, in which case only that project will be converted. This
116 path or one of its parent directories has to contain a subdirectory
117 called CVSROOT (though the CVSROOT directory can be empty).
119 Multiple CVS repositories can be converted into a single Subversion
120 repository in a single run of cvs2svn, but only by using an
121 \\fB--options\\fR file.
124 files = """\
125 A directory called \\fIcvs2svn-tmp\\fR (or the directory specified by
126 \\fB--tmpdir\\fR) is used as scratch space for temporary data files.
129 see_also = [
130 ('cvs', '1'),
131 ('svn', '1'),
132 ('svnadmin', '1'),
135 def _get_output_options_group(self):
136 group = super(SVNRunOptions, self)._get_output_options_group()
138 group.add_option(IncompatibleOption(
139 '--svnrepos', '-s', type='string',
140 action='store',
141 help='path where SVN repos should be created',
142 man_help=(
143 'Write the output of the conversion into a Subversion repository '
144 'located at \\fIpath\\fR. This option causes a new Subversion '
145 'repository to be created at \\fIpath\\fR unless the '
146 '\\fB--existing-svnrepos\\fR option is also used.'
148 metavar='PATH',
150 self.parser.set_default('existing_svnrepos', False)
151 group.add_option(IncompatibleOption(
152 '--existing-svnrepos',
153 action='store_true',
154 help='load into existing SVN repository (for use with --svnrepos)',
155 man_help=(
156 'Load the converted CVS repository into an existing Subversion '
157 'repository, instead of creating a new repository. (This option '
158 'should be used in combination with '
159 '\\fB-s\\fR/\\fB--svnrepos\\fR.) The repository must either be '
160 'empty or contain no paths that overlap with those that will '
161 'result from the conversion. Please note that you need write '
162 'permission for the repository files.'
165 group.add_option(IncompatibleOption(
166 '--fs-type', type='string',
167 action='store',
168 help=(
169 'pass --fs-type=TYPE to "svnadmin create" (for use with '
170 '--svnrepos)'
172 man_help=(
173 'Pass \\fI--fs-type\\fR=\\fItype\\fR to "svnadmin create" when '
174 'creating a new repository.'
176 metavar='TYPE',
178 self.parser.set_default('bdb_txn_nosync', False)
179 group.add_option(IncompatibleOption(
180 '--bdb-txn-nosync',
181 action='store_true',
182 help=(
183 'pass --bdb-txn-nosync to "svnadmin create" (for use with '
184 '--svnrepos)'
186 man_help=(
187 'Pass \\fI--bdb-txn-nosync\\fR to "svnadmin create" when '
188 'creating a new BDB-style Subversion repository.'
191 self.parser.set_default('create_options', [])
192 group.add_option(IncompatibleOption(
193 '--create-option', type='string',
194 action='append', dest='create_options',
195 help='pass OPT to "svnadmin create" (for use with --svnrepos)',
196 man_help=(
197 'Pass \\fIopt\\fR to "svnadmin create" when creating a new '
198 'Subversion repository (can be specified multiple times to '
199 'pass multiple options).'
201 metavar='OPT',
203 group.add_option(IncompatibleOption(
204 '--dumpfile', type='string',
205 action='store',
206 help='just produce a dumpfile; don\'t commit to a repos',
207 man_help=(
208 'Just produce a dumpfile; don\'t commit to an SVN repository. '
209 'Write the dumpfile to \\fIpath\\fR.'
211 metavar='PATH',
214 group.add_option(ContextOption(
215 '--dry-run',
216 action='store_true',
217 help=(
218 'do not create a repository or a dumpfile; just print what '
219 'would happen.'
221 man_help=(
222 'Do not create a repository or a dumpfile; just print the '
223 'details of what cvs2svn would do if it were really converting '
224 'your repository.'
228 # Deprecated options:
229 self.parser.set_default('dump_only', False)
230 group.add_option(IncompatibleOption(
231 '--dump-only',
232 action='callback', callback=self.callback_dump_only,
233 help=optparse.SUPPRESS_HELP,
234 man_help=optparse.SUPPRESS_HELP,
236 group.add_option(IncompatibleOption(
237 '--create',
238 action='callback', callback=self.callback_create,
239 help=optparse.SUPPRESS_HELP,
240 man_help=optparse.SUPPRESS_HELP,
243 return group
245 def _get_conversion_options_group(self):
246 group = super(SVNRunOptions, self)._get_conversion_options_group()
248 self.parser.set_default('trunk_base', config.DEFAULT_TRUNK_BASE)
249 group.add_option(IncompatibleOption(
250 '--trunk', type='string',
251 action='store', dest='trunk_base',
252 help=(
253 'path for trunk (default: %s)'
254 % (config.DEFAULT_TRUNK_BASE,)
256 man_help=(
257 'Set the top-level path to use for trunk in the Subversion '
258 'repository. The default is \\fI%s\\fR.'
259 % (config.DEFAULT_TRUNK_BASE,)
261 metavar='PATH',
263 self.parser.set_default('branches_base', config.DEFAULT_BRANCHES_BASE)
264 group.add_option(IncompatibleOption(
265 '--branches', type='string',
266 action='store', dest='branches_base',
267 help=(
268 'path for branches (default: %s)'
269 % (config.DEFAULT_BRANCHES_BASE,)
271 man_help=(
272 'Set the top-level path to use for branches in the Subversion '
273 'repository. The default is \\fI%s\\fR.'
274 % (config.DEFAULT_BRANCHES_BASE,)
276 metavar='PATH',
278 self.parser.set_default('tags_base', config.DEFAULT_TAGS_BASE)
279 group.add_option(IncompatibleOption(
280 '--tags', type='string',
281 action='store', dest='tags_base',
282 help=(
283 'path for tags (default: %s)'
284 % (config.DEFAULT_TAGS_BASE,)
286 man_help=(
287 'Set the top-level path to use for tags in the Subversion '
288 'repository. The default is \\fI%s\\fR.'
289 % (config.DEFAULT_TAGS_BASE,)
291 metavar='PATH',
293 group.add_option(ContextOption(
294 '--include-empty-directories',
295 action='store_true', dest='include_empty_directories',
296 help=(
297 'include empty directories within the CVS repository '
298 'in the conversion'
300 man_help=(
301 'Treat empty subdirectories within the CVS repository as actual '
302 'directories, creating them when the parent directory is created '
303 'and removing them if and when the parent directory is pruned.'
306 group.add_option(ContextOption(
307 '--no-prune',
308 action='store_false', dest='prune',
309 help='don\'t prune empty directories',
310 man_help=(
311 'When all files are deleted from a directory in the Subversion '
312 'repository, don\'t delete the empty directory (the default is '
313 'to delete any empty directories).'
316 group.add_option(ContextOption(
317 '--no-cross-branch-commits',
318 action='store_false', dest='cross_branch_commits',
319 help='prevent the creation of cross-branch commits',
320 man_help=(
321 'Prevent the creation of commits that affect files on multiple '
322 'branches at once.'
326 return group
328 def _get_extraction_options_group(self):
329 group = super(SVNRunOptions, self)._get_extraction_options_group()
330 self._add_use_internal_co_option(group)
331 self._add_use_cvs_option(group)
332 self._add_use_rcs_option(group)
333 return group
335 def _get_environment_options_group(self):
336 group = super(SVNRunOptions, self)._get_environment_options_group()
338 group.add_option(ContextOption(
339 '--svnadmin', type='string',
340 action='store', dest='svnadmin_executable',
341 help='path to the "svnadmin" program',
342 man_help=(
343 'Path to the \\fIsvnadmin\\fR program. (\\fIsvnadmin\\fR is '
344 'needed when the \\fB-s\\fR/\\fB--svnrepos\\fR output option is '
345 'used.)'
347 metavar='PATH',
348 compatible_with_option=True,
351 return group
353 def callback_dump_only(self, option, opt_str, value, parser):
354 parser.values.dump_only = True
355 logger.error(
356 warning_prefix +
357 ': The --dump-only option is deprecated (it is implied '
358 'by --dumpfile).\n'
361 def callback_create(self, option, opt_str, value, parser):
362 logger.error(
363 warning_prefix +
364 ': The behaviour produced by the --create option is now the '
365 'default;\n'
366 'passing the option is deprecated.\n'
369 def process_extraction_options(self):
370 """Process options related to extracting data from the CVS repository."""
371 self.process_all_extraction_options()
373 def process_output_options(self):
374 """Process the options related to SVN output."""
376 ctx = Ctx()
377 options = self.options
379 if options.dump_only and not options.dumpfile:
380 raise FatalError("'--dump-only' requires '--dumpfile' to be specified.")
382 if not options.svnrepos and not options.dumpfile and not ctx.dry_run:
383 raise FatalError("must pass one of '-s' or '--dumpfile'.")
385 not_both(options.svnrepos, '-s',
386 options.dumpfile, '--dumpfile')
388 not_both(options.dumpfile, '--dumpfile',
389 options.existing_svnrepos, '--existing-svnrepos')
391 not_both(options.bdb_txn_nosync, '--bdb-txn-nosync',
392 options.existing_svnrepos, '--existing-svnrepos')
394 not_both(options.dumpfile, '--dumpfile',
395 options.bdb_txn_nosync, '--bdb-txn-nosync')
397 not_both(options.fs_type, '--fs-type',
398 options.existing_svnrepos, '--existing-svnrepos')
400 if (
401 options.fs_type
402 and options.fs_type != 'bdb'
403 and options.bdb_txn_nosync
405 raise FatalError("cannot pass --bdb-txn-nosync with --fs-type=%s."
406 % options.fs_type)
408 if options.svnrepos:
409 if options.existing_svnrepos:
410 ctx.output_option = ExistingRepositoryOutputOption(options.svnrepos)
411 else:
412 ctx.output_option = NewRepositoryOutputOption(
413 options.svnrepos,
414 fs_type=options.fs_type, bdb_txn_nosync=options.bdb_txn_nosync,
415 create_options=options.create_options)
416 else:
417 ctx.output_option = DumpfileOutputOption(options.dumpfile)
419 def add_project(
420 self,
421 project_cvs_repos_path,
422 trunk_path=None, branches_path=None, tags_path=None,
423 initial_directories=[],
424 symbol_transforms=None,
425 symbol_strategy_rules=[],
427 """Add a project to be converted.
429 Most arguments are passed straight through to the Project
430 constructor. SYMBOL_STRATEGY_RULES is an iterable of
431 SymbolStrategyRules that will be applied to symbols in this
432 project."""
434 if trunk_path is not None:
435 trunk_path = normalize_svn_path(trunk_path, allow_empty=True)
436 if branches_path is not None:
437 branches_path = normalize_svn_path(branches_path, allow_empty=False)
438 if tags_path is not None:
439 tags_path = normalize_svn_path(tags_path, allow_empty=False)
441 initial_directories = [
442 path
443 for path in [trunk_path, branches_path, tags_path]
444 if path
445 ] + [
446 normalize_svn_path(path)
447 for path in initial_directories
450 symbol_strategy_rules = list(symbol_strategy_rules)
452 # Add rules to set the SVN paths for LODs depending on whether
453 # they are the trunk, tags, or branches:
454 if trunk_path is not None:
455 symbol_strategy_rules.append(TrunkPathRule(trunk_path))
456 if branches_path is not None:
457 symbol_strategy_rules.append(BranchesPathRule(branches_path))
458 if tags_path is not None:
459 symbol_strategy_rules.append(TagsPathRule(tags_path))
461 id = len(self.projects)
462 project = Project(
464 project_cvs_repos_path,
465 initial_directories=initial_directories,
466 symbol_transforms=symbol_transforms,
469 self.projects.append(project)
470 self.project_symbol_strategy_rules.append(symbol_strategy_rules)
472 def clear_projects(self):
473 """Clear the list of projects to be converted.
475 This method is for the convenience of options files, which may
476 want to import one another."""
478 del self.projects[:]
479 del self.project_symbol_strategy_rules[:]
481 def process_property_setter_options(self):
482 super(SVNRunOptions, self).process_property_setter_options()
484 # Property setters for internal use:
485 Ctx().file_property_setters.append(SVNEOLFixPropertySetter())
486 Ctx().file_property_setters.append(SVNKeywordHandlingPropertySetter())
488 def process_options(self):
489 # Consistency check for options and arguments.
490 if len(self.args) == 0:
491 self.usage()
492 sys.exit(1)
494 if len(self.args) > 1:
495 logger.error(error_prefix + ": must pass only one CVS repository.\n")
496 self.usage()
497 sys.exit(1)
499 cvsroot = self.args[0]
501 self.process_extraction_options()
502 self.process_output_options()
503 self.process_symbol_strategy_options()
504 self.process_property_setter_options()
506 # Create the default project (using ctx.trunk, ctx.branches, and
507 # ctx.tags):
508 self.add_project(
509 cvsroot,
510 trunk_path=self.options.trunk_base,
511 branches_path=self.options.branches_base,
512 tags_path=self.options.tags_base,
513 symbol_transforms=self.options.symbol_transforms,
514 symbol_strategy_rules=self.options.symbol_strategy_rules,