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."""
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 logger
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 how to munge EOLs
49 when generating the fulltext, based on how svn:eol-style is set. If
50 svn:eol-style is not set, it does _eol_style to None, thereby
51 disabling any EOL munging."""
53 # A mapping from the value of the svn:eol-style property to the EOL
54 # string that should appear in a dumpfile:
62 def set_properties(self
, cvs_file
):
63 # Fix EOLs if necessary:
64 eol_style
= cvs_file
.properties
.get('svn:eol-style', None)
66 self
.maybe_set_property(
67 cvs_file
, '_eol_fix', self
.EOL_REPLACEMENTS
[eol_style
]
70 self
.maybe_set_property(
71 cvs_file
, '_eol_fix', None
75 class SVNKeywordHandlingPropertySetter(FilePropertySetter
):
76 """Set _keyword_handling property based on the file mode and svn:keywords.
78 This setting tells the RevisionReader that it has to collapse RCS
79 keywords when generating the fulltext."""
81 def set_properties(self
, cvs_file
):
82 if cvs_file
.mode
== 'b' or cvs_file
.mode
== 'o':
83 # Leave keywords in the form that they were checked in.
85 elif cvs_file
.mode
== 'k':
86 # This mode causes CVS to collapse keywords on checkout, so we
89 elif cvs_file
.properties
.get('svn:keywords'):
90 # Subversion is going to expand the keywords, so they have to be
91 # collapsed in the dumpfile:
94 # CVS expands keywords, so we will too.
97 self
.maybe_set_property(cvs_file
, '_keyword_handling', value
)
100 class SVNRunOptions(RunOptions
):
102 Convert a CVS repository into a Subversion repository, including history.
105 short_desc
= 'convert a CVS repository into a Subversion repository'
109 [\\fIOPTION\\fR]... \\fIOUTPUT-OPTION\\fR [\\fICVS-REPOS-PATH\\fR]
112 [\\fIOPTION\\fR]... \\fI--options=PATH\\fR
116 Create a new Subversion repository based on the version history stored in a
117 CVS repository. Each CVS commit will be mirrored in the Subversion
118 repository, including such information as date of commit and id of the
121 \\fICVS-REPOS-PATH\\fR is the filesystem path of the part of the CVS
122 repository that you want to convert. It is not possible to convert a
123 CVS repository to which you only have remote access; see the FAQ for
124 more information. This path doesn't have to be the top level
125 directory of a CVS repository; it can point at a project within a
126 repository, in which case only that project will be converted. This
127 path or one of its parent directories has to contain a subdirectory
128 called CVSROOT (though the CVSROOT directory can be empty). If omitted,
129 the repository path defaults to the current directory.
131 Multiple CVS repositories can be converted into a single Subversion
132 repository in a single run of cvs2svn, but only by using an
133 \\fB--options\\fR file.
137 A directory under \\fI%s\\fR (or the directory specified by
138 \\fB--tmpdir\\fR) is used as scratch space for temporary data files.
139 """ % (tempfile
.gettempdir(),)
147 DEFAULT_USERNAME
= None
149 def _get_output_options_group(self
):
150 group
= RunOptions
._get
_output
_options
_group
(self
)
152 group
.add_option(IncompatibleOption(
153 '--svnrepos', '-s', type='string',
155 help='path where SVN repos should be created',
157 'Write the output of the conversion into a Subversion repository '
158 'located at \\fIpath\\fR. This option causes a new Subversion '
159 'repository to be created at \\fIpath\\fR unless the '
160 '\\fB--existing-svnrepos\\fR option is also used.'
164 self
.parser
.set_default('existing_svnrepos', False)
165 group
.add_option(IncompatibleOption(
166 '--existing-svnrepos',
168 help='load into existing SVN repository (for use with --svnrepos)',
170 'Load the converted CVS repository into an existing Subversion '
171 'repository, instead of creating a new repository. (This option '
172 'should be used in combination with '
173 '\\fB-s\\fR/\\fB--svnrepos\\fR.) The repository must either be '
174 'empty or contain no paths that overlap with those that will '
175 'result from the conversion. Please note that you need write '
176 'permission for the repository files.'
179 group
.add_option(IncompatibleOption(
180 '--fs-type', type='string',
183 'pass --fs-type=TYPE to "svnadmin create" (for use with '
187 'Pass \\fI--fs-type\\fR=\\fItype\\fR to "svnadmin create" when '
188 'creating a new repository.'
192 self
.parser
.set_default('bdb_txn_nosync', False)
193 group
.add_option(IncompatibleOption(
197 'pass --bdb-txn-nosync to "svnadmin create" (for use with '
201 'Pass \\fI--bdb-txn-nosync\\fR to "svnadmin create" when '
202 'creating a new BDB-style Subversion repository.'
205 self
.parser
.set_default('create_options', [])
206 group
.add_option(IncompatibleOption(
207 '--create-option', type='string',
208 action
='append', dest
='create_options',
209 help='pass OPT to "svnadmin create" (for use with --svnrepos)',
211 'Pass \\fIopt\\fR to "svnadmin create" when creating a new '
212 'Subversion repository (can be specified multiple times to '
213 'pass multiple options).'
217 group
.add_option(IncompatibleOption(
218 '--dumpfile', type='string',
220 help='just produce a dumpfile; don\'t commit to a repos',
222 'Just produce a dumpfile; don\'t commit to an SVN repository. '
223 'Write the dumpfile to \\fIpath\\fR.'
228 group
.add_option(ContextOption(
232 'do not create a repository or a dumpfile; just print what '
236 'Do not create a repository or a dumpfile; just print the '
237 'details of what cvs2svn would do if it were really converting '
242 # Deprecated options:
243 self
.parser
.set_default('dump_only', False)
244 group
.add_option(IncompatibleOption(
246 action
='callback', callback
=self
.callback_dump_only
,
247 help=optparse
.SUPPRESS_HELP
,
248 man_help
=optparse
.SUPPRESS_HELP
,
250 group
.add_option(IncompatibleOption(
252 action
='callback', callback
=self
.callback_create
,
253 help=optparse
.SUPPRESS_HELP
,
254 man_help
=optparse
.SUPPRESS_HELP
,
259 def _get_conversion_options_group(self
):
260 group
= RunOptions
._get
_conversion
_options
_group
(self
)
262 self
.parser
.set_default('trunk_base', config
.DEFAULT_TRUNK_BASE
)
263 group
.add_option(IncompatibleOption(
264 '--trunk', type='string',
265 action
='store', dest
='trunk_base',
267 'path for trunk (default: %s)'
268 % (config
.DEFAULT_TRUNK_BASE
,)
271 'Set the top-level path to use for trunk in the Subversion '
272 'repository. The default is \\fI%s\\fR.'
273 % (config
.DEFAULT_TRUNK_BASE
,)
277 self
.parser
.set_default('branches_base', config
.DEFAULT_BRANCHES_BASE
)
278 group
.add_option(IncompatibleOption(
279 '--branches', type='string',
280 action
='store', dest
='branches_base',
282 'path for branches (default: %s)'
283 % (config
.DEFAULT_BRANCHES_BASE
,)
286 'Set the top-level path to use for branches in the Subversion '
287 'repository. The default is \\fI%s\\fR.'
288 % (config
.DEFAULT_BRANCHES_BASE
,)
292 self
.parser
.set_default('tags_base', config
.DEFAULT_TAGS_BASE
)
293 group
.add_option(IncompatibleOption(
294 '--tags', type='string',
295 action
='store', dest
='tags_base',
297 'path for tags (default: %s)'
298 % (config
.DEFAULT_TAGS_BASE
,)
301 'Set the top-level path to use for tags in the Subversion '
302 'repository. The default is \\fI%s\\fR.'
303 % (config
.DEFAULT_TAGS_BASE
,)
307 group
.add_option(ContextOption(
308 '--include-empty-directories',
309 action
='store_true', dest
='include_empty_directories',
311 'include empty directories within the CVS repository '
315 'Treat empty subdirectories within the CVS repository as actual '
316 'directories, creating them when the parent directory is created '
317 'and removing them if and when the parent directory is pruned.'
320 group
.add_option(ContextOption(
322 action
='store_false', dest
='prune',
323 help='don\'t prune empty directories',
325 'When all files are deleted from a directory in the Subversion '
326 'repository, don\'t delete the empty directory (the default is '
327 'to delete any empty directories).'
330 group
.add_option(ContextOption(
331 '--no-cross-branch-commits',
332 action
='store_false', dest
='cross_branch_commits',
333 help='prevent the creation of cross-branch commits',
335 'Prevent the creation of commits that affect files on multiple '
342 def _get_extraction_options_group(self
):
343 group
= RunOptions
._get
_extraction
_options
_group
(self
)
344 self
._add
_use
_internal
_co
_option
(group
)
345 self
._add
_use
_cvs
_option
(group
)
346 self
._add
_use
_rcs
_option
(group
)
349 def _get_environment_options_group(self
):
350 group
= RunOptions
._get
_environment
_options
_group
(self
)
352 group
.add_option(ContextOption(
353 '--svnadmin', type='string',
354 action
='store', dest
='svnadmin_executable',
355 help='path to the "svnadmin" program',
357 'Path to the \\fIsvnadmin\\fR program. (\\fIsvnadmin\\fR is '
358 'needed when the \\fB-s\\fR/\\fB--svnrepos\\fR output option is '
362 compatible_with_option
=True,
367 def callback_dump_only(self
, option
, opt_str
, value
, parser
):
368 parser
.values
.dump_only
= True
371 ': The --dump-only option is deprecated (it is implied '
375 def callback_create(self
, option
, opt_str
, value
, parser
):
378 ': The behaviour produced by the --create option is now the '
380 'passing the option is deprecated.\n'
383 def process_extraction_options(self
):
384 """Process options related to extracting data from the CVS repository."""
385 self
.process_all_extraction_options()
387 def process_output_options(self
):
388 """Process the options related to SVN output."""
391 options
= self
.options
393 if options
.dump_only
and not options
.dumpfile
:
394 raise FatalError("'--dump-only' requires '--dumpfile' to be specified.")
396 if not options
.svnrepos
and not options
.dumpfile
and not ctx
.dry_run
:
397 raise FatalError("must pass one of '-s' or '--dumpfile'.")
399 not_both(options
.svnrepos
, '-s',
400 options
.dumpfile
, '--dumpfile')
402 not_both(options
.dumpfile
, '--dumpfile',
403 options
.existing_svnrepos
, '--existing-svnrepos')
405 not_both(options
.bdb_txn_nosync
, '--bdb-txn-nosync',
406 options
.existing_svnrepos
, '--existing-svnrepos')
408 not_both(options
.dumpfile
, '--dumpfile',
409 options
.bdb_txn_nosync
, '--bdb-txn-nosync')
411 not_both(options
.fs_type
, '--fs-type',
412 options
.existing_svnrepos
, '--existing-svnrepos')
416 and options
.fs_type
!= 'bdb'
417 and options
.bdb_txn_nosync
419 raise FatalError("cannot pass --bdb-txn-nosync with --fs-type=%s."
423 if options
.existing_svnrepos
:
424 ctx
.output_option
= ExistingRepositoryOutputOption(options
.svnrepos
)
426 ctx
.output_option
= NewRepositoryOutputOption(
428 fs_type
=options
.fs_type
, bdb_txn_nosync
=options
.bdb_txn_nosync
,
429 create_options
=options
.create_options
)
431 ctx
.output_option
= DumpfileOutputOption(options
.dumpfile
)
435 project_cvs_repos_path
,
436 trunk_path
=None, branches_path
=None, tags_path
=None,
437 initial_directories
=[],
438 symbol_transforms
=None,
439 symbol_strategy_rules
=[],
442 """Add a project to be converted.
444 Most arguments are passed straight through to the Project
445 constructor. SYMBOL_STRATEGY_RULES is an iterable of
446 SymbolStrategyRules that will be applied to symbols in this
449 if trunk_path
is not None:
450 trunk_path
= normalize_svn_path(trunk_path
, allow_empty
=True)
451 if branches_path
is not None:
452 branches_path
= normalize_svn_path(branches_path
, allow_empty
=False)
453 if tags_path
is not None:
454 tags_path
= normalize_svn_path(tags_path
, allow_empty
=False)
456 initial_directories
= [
458 for path
in [trunk_path
, branches_path
, tags_path
]
461 normalize_svn_path(path
)
462 for path
in initial_directories
465 symbol_strategy_rules
= list(symbol_strategy_rules
)
467 # Add rules to set the SVN paths for LODs depending on whether
468 # they are the trunk, tags, or branches:
469 if trunk_path
is not None:
470 symbol_strategy_rules
.append(TrunkPathRule(trunk_path
))
471 if branches_path
is not None:
472 symbol_strategy_rules
.append(BranchesPathRule(branches_path
))
473 if tags_path
is not None:
474 symbol_strategy_rules
.append(TagsPathRule(tags_path
))
476 id = len(self
.projects
)
479 project_cvs_repos_path
,
480 initial_directories
=initial_directories
,
481 symbol_transforms
=symbol_transforms
,
482 exclude_paths
=exclude_paths
,
485 self
.projects
.append(project
)
486 self
.project_symbol_strategy_rules
.append(symbol_strategy_rules
)
488 def clear_projects(self
):
489 """Clear the list of projects to be converted.
491 This method is for the convenience of options files, which may
492 want to import one another."""
495 del self
.project_symbol_strategy_rules
[:]
497 def process_property_setter_options(self
):
498 RunOptions
.process_property_setter_options(self
)
500 # Property setters for internal use:
501 Ctx().file_property_setters
.append(SVNEOLFixPropertySetter())
502 Ctx().file_property_setters
.append(SVNKeywordHandlingPropertySetter())
504 def process_options(self
):
505 # Consistency check for options and arguments.
506 if len(self
.args
) == 0:
510 if len(self
.args
) > 1:
511 logger
.error(error_prefix
+ ": must pass only one CVS repository.\n")
515 cvsroot
= self
.args
[0]
517 self
.process_extraction_options()
518 self
.process_output_options()
519 self
.process_symbol_strategy_options()
520 self
.process_property_setter_options()
522 # Create the default project (using ctx.trunk, ctx.branches, and
526 trunk_path
=self
.options
.trunk_base
,
527 branches_path
=self
.options
.branches_base
,
528 tags_path
=self
.options
.tags_base
,
529 symbol_transforms
=self
.options
.symbol_transforms
,
530 symbol_strategy_rules
=self
.options
.symbol_strategy_rules
,