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
.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:
62 def set_properties(self
, cvs_file
):
63 if self
.propname
in cvs_file
.properties
:
66 # Convert all EOLs to LFs if neccessary
67 eol_style
= cvs_file
.properties
.get('svn:eol-style', None)
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
:
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,
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'
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'
105 [\\fIOPTION\\fR]... \\fIOUTPUT-OPTION CVS-REPOS-PATH\\fR
108 [\\fIOPTION\\fR]... \\fI--options=PATH\\fR
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
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.
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.
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',
148 help='path where SVN repos should be created',
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.'
157 self
.parser
.set_default('existing_svnrepos', False)
158 group
.add_option(IncompatibleOption(
159 '--existing-svnrepos',
161 help='load into existing SVN repository (for use with --svnrepos)',
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',
176 'pass --fs-type=TYPE to "svnadmin create" (for use with '
180 'Pass \\fI--fs-type\\fR=\\fItype\\fR to "svnadmin create" when '
181 'creating a new repository.'
185 self
.parser
.set_default('bdb_txn_nosync', False)
186 group
.add_option(IncompatibleOption(
190 'pass --bdb-txn-nosync to "svnadmin create" (for use with '
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)',
204 'Pass \\fIopt\\fR to "svnadmin create" when creating a new '
205 'Subversion repository (can be specified multiple times to '
206 'pass multiple options).'
210 group
.add_option(IncompatibleOption(
211 '--dumpfile', type='string',
213 help='just produce a dumpfile; don\'t commit to a repos',
215 'Just produce a dumpfile; don\'t commit to an SVN repository. '
216 'Write the dumpfile to \\fIpath\\fR.'
221 group
.add_option(ContextOption(
225 'do not create a repository or a dumpfile; just print what '
229 'Do not create a repository or a dumpfile; just print the '
230 'details of what cvs2svn would do if it were really converting '
235 # Deprecated options:
236 self
.parser
.set_default('dump_only', False)
237 group
.add_option(IncompatibleOption(
239 action
='callback', callback
=self
.callback_dump_only
,
240 help=optparse
.SUPPRESS_HELP
,
241 man_help
=optparse
.SUPPRESS_HELP
,
243 group
.add_option(IncompatibleOption(
245 action
='callback', callback
=self
.callback_create
,
246 help=optparse
.SUPPRESS_HELP
,
247 man_help
=optparse
.SUPPRESS_HELP
,
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',
260 'path for trunk (default: %s)'
261 % (config
.DEFAULT_TRUNK_BASE
,)
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
,)
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',
275 'path for branches (default: %s)'
276 % (config
.DEFAULT_BRANCHES_BASE
,)
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
,)
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',
290 'path for tags (default: %s)'
291 % (config
.DEFAULT_TAGS_BASE
,)
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
,)
300 group
.add_option(ContextOption(
301 '--include-empty-directories',
302 action
='store_true', dest
='include_empty_directories',
304 'include empty directories within the CVS repository '
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(
315 action
='store_false', dest
='prune',
316 help='don\'t prune empty directories',
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',
328 'Prevent the creation of commits that affect files on multiple '
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
)
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',
350 'Path to the \\fIsvnadmin\\fR program. (\\fIsvnadmin\\fR is '
351 'needed when the \\fB-s\\fR/\\fB--svnrepos\\fR output option is '
359 def callback_dump_only(self
, option
, opt_str
, value
, parser
):
360 parser
.values
.dump_only
= True
363 ': The --dump-only option is deprecated (it is implied '
367 def callback_create(self
, option
, opt_str
, value
, parser
):
370 ': The behaviour produced by the --create option is now the '
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."""
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')
408 and options
.fs_type
!= 'bdb'
409 and options
.bdb_txn_nosync
411 raise FatalError("cannot pass --bdb-txn-nosync with --fs-type=%s."
415 if options
.existing_svnrepos
:
416 ctx
.output_option
= ExistingRepositoryOutputOption(options
.svnrepos
)
418 ctx
.output_option
= NewRepositoryOutputOption(
420 fs_type
=options
.fs_type
, bdb_txn_nosync
=options
.bdb_txn_nosync
,
421 create_options
=options
.create_options
)
423 ctx
.output_option
= DumpfileOutputOption(options
.dumpfile
)
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
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
= [
449 for path
in [trunk_path
, branches_path
, tags_path
]
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
)
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."""
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:
500 if len(self
.args
) > 1:
501 Log().error(error_prefix
+ ": must pass only one CVS repository.\n")
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
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
,