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."""
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:
59 def set_properties(self
, cvs_file
):
60 # Fix EOLs if necessary:
61 eol_style
= cvs_file
.properties
.get('svn:eol-style', None)
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.
78 elif cvs_file
.mode
== 'k':
79 # This mode causes CVS to collapse keywords on checkout, so we
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:
87 # CVS expands keywords, so we will too.
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'
98 [\\fIOPTION\\fR]... \\fIOUTPUT-OPTION CVS-REPOS-PATH\\fR
101 [\\fIOPTION\\fR]... \\fI--options=PATH\\fR
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
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.
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.
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',
141 help='path where SVN repos should be created',
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.'
150 self
.parser
.set_default('existing_svnrepos', False)
151 group
.add_option(IncompatibleOption(
152 '--existing-svnrepos',
154 help='load into existing SVN repository (for use with --svnrepos)',
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',
169 'pass --fs-type=TYPE to "svnadmin create" (for use with '
173 'Pass \\fI--fs-type\\fR=\\fItype\\fR to "svnadmin create" when '
174 'creating a new repository.'
178 self
.parser
.set_default('bdb_txn_nosync', False)
179 group
.add_option(IncompatibleOption(
183 'pass --bdb-txn-nosync to "svnadmin create" (for use with '
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)',
197 'Pass \\fIopt\\fR to "svnadmin create" when creating a new '
198 'Subversion repository (can be specified multiple times to '
199 'pass multiple options).'
203 group
.add_option(IncompatibleOption(
204 '--dumpfile', type='string',
206 help='just produce a dumpfile; don\'t commit to a repos',
208 'Just produce a dumpfile; don\'t commit to an SVN repository. '
209 'Write the dumpfile to \\fIpath\\fR.'
214 group
.add_option(ContextOption(
218 'do not create a repository or a dumpfile; just print what '
222 'Do not create a repository or a dumpfile; just print the '
223 'details of what cvs2svn would do if it were really converting '
228 # Deprecated options:
229 self
.parser
.set_default('dump_only', False)
230 group
.add_option(IncompatibleOption(
232 action
='callback', callback
=self
.callback_dump_only
,
233 help=optparse
.SUPPRESS_HELP
,
234 man_help
=optparse
.SUPPRESS_HELP
,
236 group
.add_option(IncompatibleOption(
238 action
='callback', callback
=self
.callback_create
,
239 help=optparse
.SUPPRESS_HELP
,
240 man_help
=optparse
.SUPPRESS_HELP
,
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',
253 'path for trunk (default: %s)'
254 % (config
.DEFAULT_TRUNK_BASE
,)
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
,)
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',
268 'path for branches (default: %s)'
269 % (config
.DEFAULT_BRANCHES_BASE
,)
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
,)
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',
283 'path for tags (default: %s)'
284 % (config
.DEFAULT_TAGS_BASE
,)
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
,)
293 group
.add_option(ContextOption(
294 '--include-empty-directories',
295 action
='store_true', dest
='include_empty_directories',
297 'include empty directories within the CVS repository '
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(
308 action
='store_false', dest
='prune',
309 help='don\'t prune empty directories',
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',
321 'Prevent the creation of commits that affect files on multiple '
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
)
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',
343 'Path to the \\fIsvnadmin\\fR program. (\\fIsvnadmin\\fR is '
344 'needed when the \\fB-s\\fR/\\fB--svnrepos\\fR output option is '
348 compatible_with_option
=True,
353 def callback_dump_only(self
, option
, opt_str
, value
, parser
):
354 parser
.values
.dump_only
= True
357 ': The --dump-only option is deprecated (it is implied '
361 def callback_create(self
, option
, opt_str
, value
, parser
):
364 ': The behaviour produced by the --create option is now the '
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."""
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')
402 and options
.fs_type
!= 'bdb'
403 and options
.bdb_txn_nosync
405 raise FatalError("cannot pass --bdb-txn-nosync with --fs-type=%s."
409 if options
.existing_svnrepos
:
410 ctx
.output_option
= ExistingRepositoryOutputOption(options
.svnrepos
)
412 ctx
.output_option
= NewRepositoryOutputOption(
414 fs_type
=options
.fs_type
, bdb_txn_nosync
=options
.bdb_txn_nosync
,
415 create_options
=options
.create_options
)
417 ctx
.output_option
= DumpfileOutputOption(options
.dumpfile
)
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
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
= [
443 for path
in [trunk_path
, branches_path
, tags_path
]
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
)
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."""
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:
494 if len(self
.args
) > 1:
495 logger
.error(error_prefix
+ ": must pass only one CVS repository.\n")
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
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
,