Skip test of executable flag if filesystem appears to be mounted noexec.
[cvs2svn.git] / cvs2svn_lib / svn_run_options.py
blob31e8daca8f5bf665c2933a7daba35f0306b2140c
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."""
19 import tempfile
21 import sys
22 import optparse
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:
55 EOL_REPLACEMENTS = {
56 'LF' : '\n',
57 'CR' : '\r',
58 'CRLF' : '\r\n',
59 'native' : '\n',
62 def set_properties(self, cvs_file):
63 # Fix EOLs if necessary:
64 eol_style = cvs_file.properties.get('svn:eol-style', None)
65 if eol_style:
66 self.maybe_set_property(
67 cvs_file, '_eol_fix', self.EOL_REPLACEMENTS[eol_style]
69 else:
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.
84 value = 'untouched'
85 elif cvs_file.mode == 'k':
86 # This mode causes CVS to collapse keywords on checkout, so we
87 # do the same:
88 value = 'collapsed'
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:
92 value = 'collapsed'
93 else:
94 # CVS expands keywords, so we will too.
95 value = 'expanded'
97 self.maybe_set_property(cvs_file, '_keyword_handling', value)
100 class SVNRunOptions(RunOptions):
101 description="""\
102 Convert a CVS repository into a Subversion repository, including history.
105 short_desc = 'convert a CVS repository into a Subversion repository'
107 synopsis = """\
108 .B cvs2svn
109 [\\fIOPTION\\fR]... \\fIOUTPUT-OPTION\\fR [\\fICVS-REPOS-PATH\\fR]
111 .B cvs2svn
112 [\\fIOPTION\\fR]... \\fI--options=PATH\\fR
115 long_desc = """\
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
119 committer.
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.
136 files = """\
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(),)
141 see_also = [
142 ('cvs', '1'),
143 ('svn', '1'),
144 ('svnadmin', '1'),
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',
154 action='store',
155 help='path where SVN repos should be created',
156 man_help=(
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.'
162 metavar='PATH',
164 self.parser.set_default('existing_svnrepos', False)
165 group.add_option(IncompatibleOption(
166 '--existing-svnrepos',
167 action='store_true',
168 help='load into existing SVN repository (for use with --svnrepos)',
169 man_help=(
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',
181 action='store',
182 help=(
183 'pass --fs-type=TYPE to "svnadmin create" (for use with '
184 '--svnrepos)'
186 man_help=(
187 'Pass \\fI--fs-type\\fR=\\fItype\\fR to "svnadmin create" when '
188 'creating a new repository.'
190 metavar='TYPE',
192 self.parser.set_default('bdb_txn_nosync', False)
193 group.add_option(IncompatibleOption(
194 '--bdb-txn-nosync',
195 action='store_true',
196 help=(
197 'pass --bdb-txn-nosync to "svnadmin create" (for use with '
198 '--svnrepos)'
200 man_help=(
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)',
210 man_help=(
211 'Pass \\fIopt\\fR to "svnadmin create" when creating a new '
212 'Subversion repository (can be specified multiple times to '
213 'pass multiple options).'
215 metavar='OPT',
217 group.add_option(IncompatibleOption(
218 '--dumpfile', type='string',
219 action='store',
220 help='just produce a dumpfile; don\'t commit to a repos',
221 man_help=(
222 'Just produce a dumpfile; don\'t commit to an SVN repository. '
223 'Write the dumpfile to \\fIpath\\fR.'
225 metavar='PATH',
228 group.add_option(ContextOption(
229 '--dry-run',
230 action='store_true',
231 help=(
232 'do not create a repository or a dumpfile; just print what '
233 'would happen.'
235 man_help=(
236 'Do not create a repository or a dumpfile; just print the '
237 'details of what cvs2svn would do if it were really converting '
238 'your repository.'
242 # Deprecated options:
243 self.parser.set_default('dump_only', False)
244 group.add_option(IncompatibleOption(
245 '--dump-only',
246 action='callback', callback=self.callback_dump_only,
247 help=optparse.SUPPRESS_HELP,
248 man_help=optparse.SUPPRESS_HELP,
250 group.add_option(IncompatibleOption(
251 '--create',
252 action='callback', callback=self.callback_create,
253 help=optparse.SUPPRESS_HELP,
254 man_help=optparse.SUPPRESS_HELP,
257 return group
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',
266 help=(
267 'path for trunk (default: %s)'
268 % (config.DEFAULT_TRUNK_BASE,)
270 man_help=(
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,)
275 metavar='PATH',
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',
281 help=(
282 'path for branches (default: %s)'
283 % (config.DEFAULT_BRANCHES_BASE,)
285 man_help=(
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,)
290 metavar='PATH',
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',
296 help=(
297 'path for tags (default: %s)'
298 % (config.DEFAULT_TAGS_BASE,)
300 man_help=(
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,)
305 metavar='PATH',
307 group.add_option(ContextOption(
308 '--include-empty-directories',
309 action='store_true', dest='include_empty_directories',
310 help=(
311 'include empty directories within the CVS repository '
312 'in the conversion'
314 man_help=(
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(
321 '--no-prune',
322 action='store_false', dest='prune',
323 help='don\'t prune empty directories',
324 man_help=(
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',
334 man_help=(
335 'Prevent the creation of commits that affect files on multiple '
336 'branches at once.'
340 return group
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)
347 return 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',
356 man_help=(
357 'Path to the \\fIsvnadmin\\fR program. (\\fIsvnadmin\\fR is '
358 'needed when the \\fB-s\\fR/\\fB--svnrepos\\fR output option is '
359 'used.)'
361 metavar='PATH',
362 compatible_with_option=True,
365 return group
367 def callback_dump_only(self, option, opt_str, value, parser):
368 parser.values.dump_only = True
369 logger.error(
370 warning_prefix +
371 ': The --dump-only option is deprecated (it is implied '
372 'by --dumpfile).\n'
375 def callback_create(self, option, opt_str, value, parser):
376 logger.error(
377 warning_prefix +
378 ': The behaviour produced by the --create option is now the '
379 'default;\n'
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."""
390 ctx = Ctx()
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')
414 if (
415 options.fs_type
416 and options.fs_type != 'bdb'
417 and options.bdb_txn_nosync
419 raise FatalError("cannot pass --bdb-txn-nosync with --fs-type=%s."
420 % options.fs_type)
422 if options.svnrepos:
423 if options.existing_svnrepos:
424 ctx.output_option = ExistingRepositoryOutputOption(options.svnrepos)
425 else:
426 ctx.output_option = NewRepositoryOutputOption(
427 options.svnrepos,
428 fs_type=options.fs_type, bdb_txn_nosync=options.bdb_txn_nosync,
429 create_options=options.create_options)
430 else:
431 ctx.output_option = DumpfileOutputOption(options.dumpfile)
433 def add_project(
434 self,
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=[],
440 exclude_paths=[],
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
447 project."""
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 = [
457 path
458 for path in [trunk_path, branches_path, tags_path]
459 if path
460 ] + [
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)
477 project = Project(
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."""
494 del self.projects[:]
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:
507 self.usage()
508 sys.exit(1)
510 if len(self.args) > 1:
511 logger.error(error_prefix + ": must pass only one CVS repository.\n")
512 self.usage()
513 sys.exit(1)
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
523 # ctx.tags):
524 self.add_project(
525 cvsroot,
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,