Mention that FilterSymbolsPass also calls the RevisionCollector callbacks.
[cvs2svn.git] / cvs2svn_lib / svn_run_options.py
blobc2e11999db0698bf2a391191341122db1834bce8
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
42 from cvs2svn_lib.property_setters import FilePropertySetter
45 class SVNEOLFixPropertySetter(FilePropertySetter):
46 """Set _eol_fix property.
48 This keyword is used to tell the RevisionReader whether it has to
49 munge EOLs when generating the fulltext."""
51 # A mapping from the value of the svn:eol-style property to the EOL
52 # string that should appear in a dumpfile:
53 EOL_REPLACEMENTS = {
54 'LF' : '\n',
55 'CR' : '\r',
56 'CRLF' : '\r\n',
57 'native' : '\n',
60 propname = '_eol_fix'
62 def set_properties(self, cvs_file):
63 if self.propname in cvs_file.properties:
64 return
66 # Convert all EOLs to LFs if neccessary
67 eol_style = cvs_file.properties.get('svn:eol-style', None)
68 if eol_style:
69 cvs_file.properties[self.propname] = self.EOL_REPLACEMENTS[eol_style]
72 class SVNKeywordHandlingPropertySetter(FilePropertySetter):
73 """Set cvs2svn:_keyword_handling=collapsed if svn:keywords is set.
75 This keyword is used to tell the RevisionReader whether it has to
76 collapse RCS keywords when generating the fulltext."""
78 propname = '_keyword_handling'
80 def set_properties(self, cvs_file):
81 if self.propname in cvs_file.properties:
82 return
84 if cvs_file.mode == 'b' or cvs_file.mode == 'o':
85 # Leave keywords in the form that they were checked in.
86 cvs_file.properties[self.propname] = 'untouched'
87 elif cvs_file.mode == 'k':
88 # The file is set in CVS to have keywords collapsed on checkout,
89 # so we do the same:
90 cvs_file.properties[self.propname] = 'collapsed'
91 elif cvs_file.properties.get('svn:keywords'):
92 # Subversion is going to expand the keywords, so they have to be
93 # collapsed in the repository:
94 cvs_file.properties[self.propname] = 'collapsed'
95 else:
96 # CVS expands keywords, so we will too.
97 cvs_file.properties[self.propname] = 'expanded'
100 class SVNRunOptions(RunOptions):
101 short_desc = 'convert a CVS repository into a Subversion repository'
103 synopsis = """\
104 .B cvs2svn
105 [\\fIOPTION\\fR]... \\fIOUTPUT-OPTION CVS-REPOS-PATH\\fR
107 .B cvs2svn
108 [\\fIOPTION\\fR]... \\fI--options=PATH\\fR
111 long_desc = """\
112 Create a new Subversion repository based on the version history stored in a
113 CVS repository. Each CVS commit will be mirrored in the Subversion
114 repository, including such information as date of commit and id of the
115 committer.
117 \\fICVS-REPOS-PATH\\fR is the filesystem path of the part of the CVS
118 repository that you want to convert. It is not possible to convert a
119 CVS repository to which you only have remote access; see the FAQ for
120 more information. This path doesn't have to be the top level
121 directory of a CVS repository; it can point at a project within a
122 repository, in which case only that project will be converted. This
123 path or one of its parent directories has to contain a subdirectory
124 called CVSROOT (though the CVSROOT directory can be empty).
126 Multiple CVS repositories can be converted into a single Subversion
127 repository in a single run of cvs2svn, but only by using an
128 \\fB--options\\fR file.
131 files = """\
132 A directory called \\fIcvs2svn-tmp\\fR (or the directory specified by
133 \\fB--tmpdir\\fR) is used as scratch space for temporary data files.
136 see_also = [
137 ('cvs', '1'),
138 ('svn', '1'),
139 ('svnadmin', '1'),
142 def _get_output_options_group(self):
143 group = super(SVNRunOptions, self)._get_output_options_group()
145 group.add_option(IncompatibleOption(
146 '--svnrepos', '-s', type='string',
147 action='store',
148 help='path where SVN repos should be created',
149 man_help=(
150 'Write the output of the conversion into a Subversion repository '
151 'located at \\fIpath\\fR. This option causes a new Subversion '
152 'repository to be created at \\fIpath\\fR unless the '
153 '\\fB--existing-svnrepos\\fR option is also used.'
155 metavar='PATH',
157 self.parser.set_default('existing_svnrepos', False)
158 group.add_option(IncompatibleOption(
159 '--existing-svnrepos',
160 action='store_true',
161 help='load into existing SVN repository (for use with --svnrepos)',
162 man_help=(
163 'Load the converted CVS repository into an existing Subversion '
164 'repository, instead of creating a new repository. (This option '
165 'should be used in combination with '
166 '\\fB-s\\fR/\\fB--svnrepos\\fR.) The repository must either be '
167 'empty or contain no paths that overlap with those that will '
168 'result from the conversion. Please note that you need write '
169 'permission for the repository files.'
172 group.add_option(IncompatibleOption(
173 '--fs-type', type='string',
174 action='store',
175 help=(
176 'pass --fs-type=TYPE to "svnadmin create" (for use with '
177 '--svnrepos)'
179 man_help=(
180 'Pass \\fI--fs-type\\fR=\\fItype\\fR to "svnadmin create" when '
181 'creating a new repository.'
183 metavar='TYPE',
185 self.parser.set_default('bdb_txn_nosync', False)
186 group.add_option(IncompatibleOption(
187 '--bdb-txn-nosync',
188 action='store_true',
189 help=(
190 'pass --bdb-txn-nosync to "svnadmin create" (for use with '
191 '--svnrepos)'
193 man_help=(
194 'Pass \\fI--bdb-txn-nosync\\fR to "svnadmin create" when '
195 'creating a new BDB-style Subversion repository.'
198 self.parser.set_default('create_options', [])
199 group.add_option(IncompatibleOption(
200 '--create-option', type='string',
201 action='append', dest='create_options',
202 help='pass OPT to "svnadmin create" (for use with --svnrepos)',
203 man_help=(
204 'Pass \\fIopt\\fR to "svnadmin create" when creating a new '
205 'Subversion repository (can be specified multiple times to '
206 'pass multiple options).'
208 metavar='OPT',
210 group.add_option(IncompatibleOption(
211 '--dumpfile', type='string',
212 action='store',
213 help='just produce a dumpfile; don\'t commit to a repos',
214 man_help=(
215 'Just produce a dumpfile; don\'t commit to an SVN repository. '
216 'Write the dumpfile to \\fIpath\\fR.'
218 metavar='PATH',
221 group.add_option(ContextOption(
222 '--dry-run',
223 action='store_true',
224 help=(
225 'do not create a repository or a dumpfile; just print what '
226 'would happen.'
228 man_help=(
229 'Do not create a repository or a dumpfile; just print the '
230 'details of what cvs2svn would do if it were really converting '
231 'your repository.'
235 # Deprecated options:
236 self.parser.set_default('dump_only', False)
237 group.add_option(IncompatibleOption(
238 '--dump-only',
239 action='callback', callback=self.callback_dump_only,
240 help=optparse.SUPPRESS_HELP,
241 man_help=optparse.SUPPRESS_HELP,
243 group.add_option(IncompatibleOption(
244 '--create',
245 action='callback', callback=self.callback_create,
246 help=optparse.SUPPRESS_HELP,
247 man_help=optparse.SUPPRESS_HELP,
250 return group
252 def _get_conversion_options_group(self):
253 group = super(SVNRunOptions, self)._get_conversion_options_group()
255 self.parser.set_default('trunk_base', config.DEFAULT_TRUNK_BASE)
256 group.add_option(IncompatibleOption(
257 '--trunk', type='string',
258 action='store', dest='trunk_base',
259 help=(
260 'path for trunk (default: %s)'
261 % (config.DEFAULT_TRUNK_BASE,)
263 man_help=(
264 'Set the top-level path to use for trunk in the Subversion '
265 'repository. The default is \\fI%s\\fR.'
266 % (config.DEFAULT_TRUNK_BASE,)
268 metavar='PATH',
270 self.parser.set_default('branches_base', config.DEFAULT_BRANCHES_BASE)
271 group.add_option(IncompatibleOption(
272 '--branches', type='string',
273 action='store', dest='branches_base',
274 help=(
275 'path for branches (default: %s)'
276 % (config.DEFAULT_BRANCHES_BASE,)
278 man_help=(
279 'Set the top-level path to use for branches in the Subversion '
280 'repository. The default is \\fI%s\\fR.'
281 % (config.DEFAULT_BRANCHES_BASE,)
283 metavar='PATH',
285 self.parser.set_default('tags_base', config.DEFAULT_TAGS_BASE)
286 group.add_option(IncompatibleOption(
287 '--tags', type='string',
288 action='store', dest='tags_base',
289 help=(
290 'path for tags (default: %s)'
291 % (config.DEFAULT_TAGS_BASE,)
293 man_help=(
294 'Set the top-level path to use for tags in the Subversion '
295 'repository. The default is \\fI%s\\fR.'
296 % (config.DEFAULT_TAGS_BASE,)
298 metavar='PATH',
300 group.add_option(ContextOption(
301 '--include-empty-directories',
302 action='store_true', dest='include_empty_directories',
303 help=(
304 'include empty directories within the CVS repository '
305 'in the conversion'
307 man_help=(
308 'Treat empty subdirectories within the CVS repository as actual '
309 'directories, creating them when the parent directory is created '
310 'and removing them if and when the parent directory is pruned.'
313 group.add_option(ContextOption(
314 '--no-prune',
315 action='store_false', dest='prune',
316 help='don\'t prune empty directories',
317 man_help=(
318 'When all files are deleted from a directory in the Subversion '
319 'repository, don\'t delete the empty directory (the default is '
320 'to delete any empty directories).'
323 group.add_option(ContextOption(
324 '--no-cross-branch-commits',
325 action='store_false', dest='cross_branch_commits',
326 help='prevent the creation of cross-branch commits',
327 man_help=(
328 'Prevent the creation of commits that affect files on multiple '
329 'branches at once.'
333 return group
335 def _get_extraction_options_group(self):
336 group = super(SVNRunOptions, self)._get_extraction_options_group()
337 self._add_use_internal_co_option(group)
338 self._add_use_cvs_option(group)
339 self._add_use_rcs_option(group)
340 return group
342 def _get_environment_options_group(self):
343 group = super(SVNRunOptions, self)._get_environment_options_group()
345 group.add_option(ContextOption(
346 '--svnadmin', type='string',
347 action='store', dest='svnadmin_executable',
348 help='path to the "svnadmin" program',
349 man_help=(
350 'Path to the \\fIsvnadmin\\fR program. (\\fIsvnadmin\\fR is '
351 'needed when the \\fB-s\\fR/\\fB--svnrepos\\fR output option is '
352 'used.)'
354 metavar='PATH',
357 return group
359 def callback_dump_only(self, option, opt_str, value, parser):
360 parser.values.dump_only = True
361 Log().error(
362 warning_prefix +
363 ': The --dump-only option is deprecated (it is implied '
364 'by --dumpfile).\n'
367 def callback_create(self, option, opt_str, value, parser):
368 Log().error(
369 warning_prefix +
370 ': The behaviour produced by the --create option is now the '
371 'default;\n'
372 'passing the option is deprecated.\n'
375 def process_extraction_options(self):
376 """Process options related to extracting data from the CVS repository."""
377 self.process_all_extraction_options()
379 def process_output_options(self):
380 """Process the options related to SVN output."""
382 ctx = Ctx()
383 options = self.options
385 if options.dump_only and not options.dumpfile:
386 raise FatalError("'--dump-only' requires '--dumpfile' to be specified.")
388 if not options.svnrepos and not options.dumpfile and not ctx.dry_run:
389 raise FatalError("must pass one of '-s' or '--dumpfile'.")
391 not_both(options.svnrepos, '-s',
392 options.dumpfile, '--dumpfile')
394 not_both(options.dumpfile, '--dumpfile',
395 options.existing_svnrepos, '--existing-svnrepos')
397 not_both(options.bdb_txn_nosync, '--bdb-txn-nosync',
398 options.existing_svnrepos, '--existing-svnrepos')
400 not_both(options.dumpfile, '--dumpfile',
401 options.bdb_txn_nosync, '--bdb-txn-nosync')
403 not_both(options.fs_type, '--fs-type',
404 options.existing_svnrepos, '--existing-svnrepos')
406 if (
407 options.fs_type
408 and options.fs_type != 'bdb'
409 and options.bdb_txn_nosync
411 raise FatalError("cannot pass --bdb-txn-nosync with --fs-type=%s."
412 % options.fs_type)
414 if options.svnrepos:
415 if options.existing_svnrepos:
416 ctx.output_option = ExistingRepositoryOutputOption(options.svnrepos)
417 else:
418 ctx.output_option = NewRepositoryOutputOption(
419 options.svnrepos,
420 fs_type=options.fs_type, bdb_txn_nosync=options.bdb_txn_nosync,
421 create_options=options.create_options)
422 else:
423 ctx.output_option = DumpfileOutputOption(options.dumpfile)
425 def add_project(
426 self,
427 project_cvs_repos_path,
428 trunk_path=None, branches_path=None, tags_path=None,
429 initial_directories=[],
430 symbol_transforms=None,
431 symbol_strategy_rules=[],
433 """Add a project to be converted.
435 Most arguments are passed straight through to the Project
436 constructor. SYMBOL_STRATEGY_RULES is an iterable of
437 SymbolStrategyRules that will be applied to symbols in this
438 project."""
440 if trunk_path is not None:
441 trunk_path = normalize_svn_path(trunk_path, allow_empty=True)
442 if branches_path is not None:
443 branches_path = normalize_svn_path(branches_path, allow_empty=False)
444 if tags_path is not None:
445 tags_path = normalize_svn_path(tags_path, allow_empty=False)
447 initial_directories = [
448 path
449 for path in [trunk_path, branches_path, tags_path]
450 if path
451 ] + [
452 normalize_svn_path(path)
453 for path in initial_directories
456 symbol_strategy_rules = list(symbol_strategy_rules)
458 # Add rules to set the SVN paths for LODs depending on whether
459 # they are the trunk, tags, or branches:
460 if trunk_path is not None:
461 symbol_strategy_rules.append(TrunkPathRule(trunk_path))
462 if branches_path is not None:
463 symbol_strategy_rules.append(BranchesPathRule(branches_path))
464 if tags_path is not None:
465 symbol_strategy_rules.append(TagsPathRule(tags_path))
467 id = len(self.projects)
468 project = Project(
470 project_cvs_repos_path,
471 initial_directories=initial_directories,
472 symbol_transforms=symbol_transforms,
475 self.projects.append(project)
476 self.project_symbol_strategy_rules.append(symbol_strategy_rules)
478 def clear_projects(self):
479 """Clear the list of projects to be converted.
481 This method is for the convenience of options files, which may
482 want to import one another."""
484 del self.projects[:]
485 del self.project_symbol_strategy_rules[:]
487 def process_property_setter_options(self):
488 super(SVNRunOptions, self).process_property_setter_options()
490 # Property setters for internal use:
491 Ctx().file_property_setters.append(SVNEOLFixPropertySetter())
492 Ctx().file_property_setters.append(SVNKeywordHandlingPropertySetter())
494 def process_options(self):
495 # Consistency check for options and arguments.
496 if len(self.args) == 0:
497 self.usage()
498 sys.exit(1)
500 if len(self.args) > 1:
501 Log().error(error_prefix + ": must pass only one CVS repository.\n")
502 self.usage()
503 sys.exit(1)
505 cvsroot = self.args[0]
507 self.process_extraction_options()
508 self.process_output_options()
509 self.process_symbol_strategy_options()
510 self.process_property_setter_options()
512 # Create the default project (using ctx.trunk, ctx.branches, and
513 # ctx.tags):
514 self.add_project(
515 cvsroot,
516 trunk_path=self.options.trunk_base,
517 branches_path=self.options.branches_base,
518 tags_path=self.options.tags_base,
519 symbol_transforms=self.options.symbol_transforms,
520 symbol_strategy_rules=self.options.symbol_strategy_rules,