Teach SVNKeywordHandlingPropertySetter about files with mode="k".
[cvs2svn.git] / cvs2svn_lib / svn_run_options.py
blobbbef9b8c2987067f6b3e1e1cebc2cd08bb0782f4
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 SVNKeywordHandlingPropertySetter(FilePropertySetter):
46 """Set cvs2svn:_keyword_handling=collapsed if svn:keywords is set.
48 This keyword is used to tell the RevisionReader whether it has to
49 collapse RCS keywords when generating the fulltext."""
51 propname = '_keyword_handling'
53 def set_properties(self, cvs_file):
54 if self.propname in cvs_file.properties:
55 return
57 if cvs_file.mode == 'k':
58 # The file is set in CVS to have keywords collapsed on checkout,
59 # so we do the same:
60 cvs_file.properties[self.propname] = 'collapsed'
61 elif cvs_file.properties.get('svn:keywords'):
62 # Subversion is going to expand the keywords, so they have to be
63 # collapsed in the repository:
64 cvs_file.properties[self.propname] = 'collapsed'
67 class SVNRunOptions(RunOptions):
68 short_desc = 'convert a CVS repository into a Subversion repository'
70 synopsis = """\
71 .B cvs2svn
72 [\\fIOPTION\\fR]... \\fIOUTPUT-OPTION CVS-REPOS-PATH\\fR
73 .br
74 .B cvs2svn
75 [\\fIOPTION\\fR]... \\fI--options=PATH\\fR
76 """
78 long_desc = """\
79 Create a new Subversion repository based on the version history stored in a
80 CVS repository. Each CVS commit will be mirrored in the Subversion
81 repository, including such information as date of commit and id of the
82 committer.
84 \\fICVS-REPOS-PATH\\fR is the filesystem path of the part of the CVS
85 repository that you want to convert. It is not possible to convert a
86 CVS repository to which you only have remote access; see the FAQ for
87 more information. This path doesn't have to be the top level
88 directory of a CVS repository; it can point at a project within a
89 repository, in which case only that project will be converted. This
90 path or one of its parent directories has to contain a subdirectory
91 called CVSROOT (though the CVSROOT directory can be empty).
93 Multiple CVS repositories can be converted into a single Subversion
94 repository in a single run of cvs2svn, but only by using an
95 \\fB--options\\fR file.
96 """
98 files = """\
99 A directory called \\fIcvs2svn-tmp\\fR (or the directory specified by
100 \\fB--tmpdir\\fR) is used as scratch space for temporary data files.
103 see_also = [
104 ('cvs', '1'),
105 ('svn', '1'),
106 ('svnadmin', '1'),
109 def _get_output_options_group(self):
110 group = super(SVNRunOptions, self)._get_output_options_group()
112 group.add_option(IncompatibleOption(
113 '--svnrepos', '-s', type='string',
114 action='store',
115 help='path where SVN repos should be created',
116 man_help=(
117 'Write the output of the conversion into a Subversion repository '
118 'located at \\fIpath\\fR. This option causes a new Subversion '
119 'repository to be created at \\fIpath\\fR unless the '
120 '\\fB--existing-svnrepos\\fR option is also used.'
122 metavar='PATH',
124 self.parser.set_default('existing_svnrepos', False)
125 group.add_option(IncompatibleOption(
126 '--existing-svnrepos',
127 action='store_true',
128 help='load into existing SVN repository (for use with --svnrepos)',
129 man_help=(
130 'Load the converted CVS repository into an existing Subversion '
131 'repository, instead of creating a new repository. (This option '
132 'should be used in combination with '
133 '\\fB-s\\fR/\\fB--svnrepos\\fR.) The repository must either be '
134 'empty or contain no paths that overlap with those that will '
135 'result from the conversion. Please note that you need write '
136 'permission for the repository files.'
139 group.add_option(IncompatibleOption(
140 '--fs-type', type='string',
141 action='store',
142 help=(
143 'pass --fs-type=TYPE to "svnadmin create" (for use with '
144 '--svnrepos)'
146 man_help=(
147 'Pass \\fI--fs-type\\fR=\\fItype\\fR to "svnadmin create" when '
148 'creating a new repository.'
150 metavar='TYPE',
152 self.parser.set_default('bdb_txn_nosync', False)
153 group.add_option(IncompatibleOption(
154 '--bdb-txn-nosync',
155 action='store_true',
156 help=(
157 'pass --bdb-txn-nosync to "svnadmin create" (for use with '
158 '--svnrepos)'
160 man_help=(
161 'Pass \\fI--bdb-txn-nosync\\fR to "svnadmin create" when '
162 'creating a new BDB-style Subversion repository.'
165 self.parser.set_default('create_options', [])
166 group.add_option(IncompatibleOption(
167 '--create-option', type='string',
168 action='append', dest='create_options',
169 help='pass OPT to "svnadmin create" (for use with --svnrepos)',
170 man_help=(
171 'Pass \\fIopt\\fR to "svnadmin create" when creating a new '
172 'Subversion repository (can be specified multiple times to '
173 'pass multiple options).'
175 metavar='OPT',
177 group.add_option(IncompatibleOption(
178 '--dumpfile', type='string',
179 action='store',
180 help='just produce a dumpfile; don\'t commit to a repos',
181 man_help=(
182 'Just produce a dumpfile; don\'t commit to an SVN repository. '
183 'Write the dumpfile to \\fIpath\\fR.'
185 metavar='PATH',
188 group.add_option(ContextOption(
189 '--dry-run',
190 action='store_true',
191 help=(
192 'do not create a repository or a dumpfile; just print what '
193 'would happen.'
195 man_help=(
196 'Do not create a repository or a dumpfile; just print the '
197 'details of what cvs2svn would do if it were really converting '
198 'your repository.'
202 # Deprecated options:
203 self.parser.set_default('dump_only', False)
204 group.add_option(IncompatibleOption(
205 '--dump-only',
206 action='callback', callback=self.callback_dump_only,
207 help=optparse.SUPPRESS_HELP,
208 man_help=optparse.SUPPRESS_HELP,
210 group.add_option(IncompatibleOption(
211 '--create',
212 action='callback', callback=self.callback_create,
213 help=optparse.SUPPRESS_HELP,
214 man_help=optparse.SUPPRESS_HELP,
217 return group
219 def _get_conversion_options_group(self):
220 group = super(SVNRunOptions, self)._get_conversion_options_group()
222 self.parser.set_default('trunk_base', config.DEFAULT_TRUNK_BASE)
223 group.add_option(IncompatibleOption(
224 '--trunk', type='string',
225 action='store', dest='trunk_base',
226 help=(
227 'path for trunk (default: %s)'
228 % (config.DEFAULT_TRUNK_BASE,)
230 man_help=(
231 'Set the top-level path to use for trunk in the Subversion '
232 'repository. The default is \\fI%s\\fR.'
233 % (config.DEFAULT_TRUNK_BASE,)
235 metavar='PATH',
237 self.parser.set_default('branches_base', config.DEFAULT_BRANCHES_BASE)
238 group.add_option(IncompatibleOption(
239 '--branches', type='string',
240 action='store', dest='branches_base',
241 help=(
242 'path for branches (default: %s)'
243 % (config.DEFAULT_BRANCHES_BASE,)
245 man_help=(
246 'Set the top-level path to use for branches in the Subversion '
247 'repository. The default is \\fI%s\\fR.'
248 % (config.DEFAULT_BRANCHES_BASE,)
250 metavar='PATH',
252 self.parser.set_default('tags_base', config.DEFAULT_TAGS_BASE)
253 group.add_option(IncompatibleOption(
254 '--tags', type='string',
255 action='store', dest='tags_base',
256 help=(
257 'path for tags (default: %s)'
258 % (config.DEFAULT_TAGS_BASE,)
260 man_help=(
261 'Set the top-level path to use for tags in the Subversion '
262 'repository. The default is \\fI%s\\fR.'
263 % (config.DEFAULT_TAGS_BASE,)
265 metavar='PATH',
267 group.add_option(ContextOption(
268 '--include-empty-directories',
269 action='store_true', dest='include_empty_directories',
270 help=(
271 'include empty directories within the CVS repository '
272 'in the conversion'
274 man_help=(
275 'Treat empty subdirectories within the CVS repository as actual '
276 'directories, creating them when the parent directory is created '
277 'and removing them if and when the parent directory is pruned.'
280 group.add_option(ContextOption(
281 '--no-prune',
282 action='store_false', dest='prune',
283 help='don\'t prune empty directories',
284 man_help=(
285 'When all files are deleted from a directory in the Subversion '
286 'repository, don\'t delete the empty directory (the default is '
287 'to delete any empty directories).'
290 group.add_option(ContextOption(
291 '--no-cross-branch-commits',
292 action='store_false', dest='cross_branch_commits',
293 help='prevent the creation of cross-branch commits',
294 man_help=(
295 'Prevent the creation of commits that affect files on multiple '
296 'branches at once.'
300 return group
302 def _get_extraction_options_group(self):
303 group = super(SVNRunOptions, self)._get_extraction_options_group()
304 self._add_use_internal_co_option(group)
305 self._add_use_cvs_option(group)
306 self._add_use_rcs_option(group)
307 return group
309 def _get_environment_options_group(self):
310 group = super(SVNRunOptions, self)._get_environment_options_group()
312 group.add_option(ContextOption(
313 '--svnadmin', type='string',
314 action='store', dest='svnadmin_executable',
315 help='path to the "svnadmin" program',
316 man_help=(
317 'Path to the \\fIsvnadmin\\fR program. (\\fIsvnadmin\\fR is '
318 'needed when the \\fB-s\\fR/\\fB--svnrepos\\fR output option is '
319 'used.)'
321 metavar='PATH',
324 return group
326 def callback_dump_only(self, option, opt_str, value, parser):
327 parser.values.dump_only = True
328 Log().error(
329 warning_prefix +
330 ': The --dump-only option is deprecated (it is implied '
331 'by --dumpfile).\n'
334 def callback_create(self, option, opt_str, value, parser):
335 Log().error(
336 warning_prefix +
337 ': The behaviour produced by the --create option is now the '
338 'default;\n'
339 'passing the option is deprecated.\n'
342 def process_extraction_options(self):
343 """Process options related to extracting data from the CVS repository."""
344 self.process_all_extraction_options()
346 def process_output_options(self):
347 """Process the options related to SVN output."""
349 ctx = Ctx()
350 options = self.options
352 if options.dump_only and not options.dumpfile:
353 raise FatalError("'--dump-only' requires '--dumpfile' to be specified.")
355 if not options.svnrepos and not options.dumpfile and not ctx.dry_run:
356 raise FatalError("must pass one of '-s' or '--dumpfile'.")
358 not_both(options.svnrepos, '-s',
359 options.dumpfile, '--dumpfile')
361 not_both(options.dumpfile, '--dumpfile',
362 options.existing_svnrepos, '--existing-svnrepos')
364 not_both(options.bdb_txn_nosync, '--bdb-txn-nosync',
365 options.existing_svnrepos, '--existing-svnrepos')
367 not_both(options.dumpfile, '--dumpfile',
368 options.bdb_txn_nosync, '--bdb-txn-nosync')
370 not_both(options.fs_type, '--fs-type',
371 options.existing_svnrepos, '--existing-svnrepos')
373 if (
374 options.fs_type
375 and options.fs_type != 'bdb'
376 and options.bdb_txn_nosync
378 raise FatalError("cannot pass --bdb-txn-nosync with --fs-type=%s."
379 % options.fs_type)
381 if options.svnrepos:
382 if options.existing_svnrepos:
383 ctx.output_option = ExistingRepositoryOutputOption(options.svnrepos)
384 else:
385 ctx.output_option = NewRepositoryOutputOption(
386 options.svnrepos,
387 fs_type=options.fs_type, bdb_txn_nosync=options.bdb_txn_nosync,
388 create_options=options.create_options)
389 else:
390 ctx.output_option = DumpfileOutputOption(options.dumpfile)
392 def add_project(
393 self,
394 project_cvs_repos_path,
395 trunk_path=None, branches_path=None, tags_path=None,
396 initial_directories=[],
397 symbol_transforms=None,
398 symbol_strategy_rules=[],
400 """Add a project to be converted.
402 Most arguments are passed straight through to the Project
403 constructor. SYMBOL_STRATEGY_RULES is an iterable of
404 SymbolStrategyRules that will be applied to symbols in this
405 project."""
407 if trunk_path is not None:
408 trunk_path = normalize_svn_path(trunk_path, allow_empty=True)
409 if branches_path is not None:
410 branches_path = normalize_svn_path(branches_path, allow_empty=False)
411 if tags_path is not None:
412 tags_path = normalize_svn_path(tags_path, allow_empty=False)
414 initial_directories = [
415 path
416 for path in [trunk_path, branches_path, tags_path]
417 if path
418 ] + [
419 normalize_svn_path(path)
420 for path in initial_directories
423 symbol_strategy_rules = list(symbol_strategy_rules)
425 # Add rules to set the SVN paths for LODs depending on whether
426 # they are the trunk, tags, or branches:
427 if trunk_path is not None:
428 symbol_strategy_rules.append(TrunkPathRule(trunk_path))
429 if branches_path is not None:
430 symbol_strategy_rules.append(BranchesPathRule(branches_path))
431 if tags_path is not None:
432 symbol_strategy_rules.append(TagsPathRule(tags_path))
434 id = len(self.projects)
435 project = Project(
437 project_cvs_repos_path,
438 initial_directories=initial_directories,
439 symbol_transforms=symbol_transforms,
442 self.projects.append(project)
443 self.project_symbol_strategy_rules.append(symbol_strategy_rules)
445 def clear_projects(self):
446 """Clear the list of projects to be converted.
448 This method is for the convenience of options files, which may
449 want to import one another."""
451 del self.projects[:]
452 del self.project_symbol_strategy_rules[:]
454 def process_property_setter_options(self):
455 super(SVNRunOptions, self).process_property_setter_options()
457 # Property setters for internal use:
458 Ctx().file_property_setters.append(SVNKeywordHandlingPropertySetter())
460 def process_options(self):
461 # Consistency check for options and arguments.
462 if len(self.args) == 0:
463 self.usage()
464 sys.exit(1)
466 if len(self.args) > 1:
467 Log().error(error_prefix + ": must pass only one CVS repository.\n")
468 self.usage()
469 sys.exit(1)
471 cvsroot = self.args[0]
473 self.process_extraction_options()
474 self.process_output_options()
475 self.process_symbol_strategy_options()
476 self.process_property_setter_options()
478 # Create the default project (using ctx.trunk, ctx.branches, and
479 # ctx.tags):
480 self.add_project(
481 cvsroot,
482 trunk_path=self.options.trunk_base,
483 branches_path=self.options.branches_base,
484 tags_path=self.options.tags_base,
485 symbol_transforms=self.options.symbol_transforms,
486 symbol_strategy_rules=self.options.symbol_strategy_rules,