Update copyright dates through whole project based on date of last commit.
[cvs2svn.git] / cvs2svn_lib / svn_run_options.py
blobe7577307a6398c6119896134219a415406b5096b
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
22 import datetime
23 import codecs
25 from cvs2svn_lib.version import VERSION
26 from cvs2svn_lib import config
27 from cvs2svn_lib.common import warning_prefix
28 from cvs2svn_lib.common import error_prefix
29 from cvs2svn_lib.common import FatalError
30 from cvs2svn_lib.common import normalize_svn_path
31 from cvs2svn_lib.log import Log
32 from cvs2svn_lib.context import Ctx
33 from cvs2svn_lib.run_options import not_both
34 from cvs2svn_lib.run_options import RunOptions
35 from cvs2svn_lib.run_options import ContextOption
36 from cvs2svn_lib.run_options import IncompatibleOption
37 from cvs2svn_lib.run_options import authors
38 from cvs2svn_lib.man_writer import ManWriter
39 from cvs2svn_lib.project import Project
40 from cvs2svn_lib.svn_output_option import DumpfileOutputOption
41 from cvs2svn_lib.svn_output_option import ExistingRepositoryOutputOption
42 from cvs2svn_lib.svn_output_option import NewRepositoryOutputOption
43 from cvs2svn_lib.revision_manager import NullRevisionRecorder
44 from cvs2svn_lib.revision_manager import NullRevisionExcluder
45 from cvs2svn_lib.rcs_revision_manager import RCSRevisionReader
46 from cvs2svn_lib.cvs_revision_manager import CVSRevisionReader
47 from cvs2svn_lib.checkout_internal import InternalRevisionRecorder
48 from cvs2svn_lib.checkout_internal import InternalRevisionExcluder
49 from cvs2svn_lib.checkout_internal import InternalRevisionReader
50 from cvs2svn_lib.symbol_strategy import TrunkPathRule
51 from cvs2svn_lib.symbol_strategy import BranchesPathRule
52 from cvs2svn_lib.symbol_strategy import TagsPathRule
55 short_desc = 'convert a cvs repository into a subversion repository'
57 synopsis = """\
58 .B cvs2svn
59 [\\fIOPTION\\fR]... \\fIOUTPUT-OPTION CVS-REPOS-PATH\\fR
60 .br
61 .B cvs2svn
62 [\\fIOPTION\\fR]... \\fI--options=PATH\\fR
63 """
65 long_desc = """\
66 Create a new Subversion repository based on the version history stored in a
67 CVS repository. Each CVS commit will be mirrored in the Subversion
68 repository, including such information as date of commit and id of the
69 committer.
71 \\fICVS-REPOS-PATH\\fR is the filesystem path of the part of the CVS
72 repository that you want to convert. It is not possible to convert a
73 CVS repository to which you only have remote access; see the FAQ for
74 more information. This path doesn't have to be the top level
75 directory of a CVS repository; it can point at a project within a
76 repository, in which case only that project will be converted. This
77 path or one of its parent directories has to contain a subdirectory
78 called CVSROOT (though the CVSROOT directory can be empty).
80 Multiple CVS repositories can be converted into a single Subversion
81 repository in a single run of cvs2svn, but only by using an
82 \\fB--options\\fR file.
83 """
85 files = """\
86 A directory called \\fIcvs2svn-tmp\\fR (or the directory specified by
87 \\fB--tmpdir\\fR) is used as scratch space for temporary data files.
88 """
90 see_also = [
91 ('cvs', '1'),
92 ('svn', '1'),
93 ('svnadmin', '1'),
97 class SVNRunOptions(RunOptions):
98 def _get_output_options_group(self):
99 group = RunOptions._get_output_options_group(self)
101 group.add_option(IncompatibleOption(
102 '--svnrepos', '-s', type='string',
103 action='store',
104 help='path where SVN repos should be created',
105 man_help=(
106 'Write the output of the conversion into a Subversion repository '
107 'located at \\fIpath\\fR. This option causes a new Subversion '
108 'repository to be created at \\fIpath\\fR unless the '
109 '\\fB--existing-svnrepos\\fR option is also used.'
111 metavar='PATH',
113 self.parser.set_default('existing_svnrepos', False)
114 group.add_option(IncompatibleOption(
115 '--existing-svnrepos',
116 action='store_true',
117 help='load into existing SVN repository (for use with --svnrepos)',
118 man_help=(
119 'Load the converted CVS repository into an existing Subversion '
120 'repository, instead of creating a new repository. (This option '
121 'should be used in combination with '
122 '\\fB-s\\fR/\\fB--svnrepos\\fR.) The repository must either be '
123 'empty or contain no paths that overlap with those that will '
124 'result from the conversion. Please note that you need write '
125 'permission for the repository files.'
128 group.add_option(IncompatibleOption(
129 '--fs-type', type='string',
130 action='store',
131 help=(
132 'pass --fs-type=TYPE to "svnadmin create" (for use with '
133 '--svnrepos)'
135 man_help=(
136 'Pass \\fI--fs-type\\fR=\\fItype\\fR to "svnadmin create" when '
137 'creating a new repository.'
139 metavar='TYPE',
141 self.parser.set_default('bdb_txn_nosync', False)
142 group.add_option(IncompatibleOption(
143 '--bdb-txn-nosync',
144 action='store_true',
145 help=(
146 'pass --bdb-txn-nosync to "svnadmin create" (for use with '
147 '--svnrepos)'
149 man_help=(
150 'Pass \\fI--bdb-txn-nosync\\fR to "svnadmin create" when '
151 'creating a new BDB-style Subversion repository.'
154 self.parser.set_default('create_options', [])
155 group.add_option(IncompatibleOption(
156 '--create-option', type='string',
157 action='append', dest='create_options',
158 help='pass OPT to "svnadmin create" (for use with --svnrepos)',
159 man_help=(
160 'Pass \\fIopt\\fR to "svnadmin create" when creating a new '
161 'Subversion repository (can be specified multiple times to '
162 'pass multiple options).'
164 metavar='OPT',
166 group.add_option(IncompatibleOption(
167 '--dumpfile', type='string',
168 action='store',
169 help='just produce a dumpfile; don\'t commit to a repos',
170 man_help=(
171 'Just produce a dumpfile; don\'t commit to an SVN repository. '
172 'Write the dumpfile to \\fIpath\\fR.'
174 metavar='PATH',
177 group.add_option(ContextOption(
178 '--dry-run',
179 action='store_true',
180 help=(
181 'do not create a repository or a dumpfile; just print what '
182 'would happen.'
184 man_help=(
185 'Do not create a repository or a dumpfile; just print the '
186 'details of what cvs2svn would do if it were really converting '
187 'your repository.'
191 # Deprecated options:
192 self.parser.set_default('dump_only', False)
193 group.add_option(IncompatibleOption(
194 '--dump-only',
195 action='callback', callback=self.callback_dump_only,
196 help=optparse.SUPPRESS_HELP,
197 man_help=optparse.SUPPRESS_HELP,
199 group.add_option(IncompatibleOption(
200 '--create',
201 action='callback', callback=self.callback_create,
202 help=optparse.SUPPRESS_HELP,
203 man_help=optparse.SUPPRESS_HELP,
206 return group
208 def _get_conversion_options_group(self):
209 group = RunOptions._get_conversion_options_group(self)
211 self.parser.set_default('trunk_base', config.DEFAULT_TRUNK_BASE)
212 group.add_option(IncompatibleOption(
213 '--trunk', type='string',
214 action='store', dest='trunk_base',
215 help=(
216 'path for trunk (default: %s)'
217 % (config.DEFAULT_TRUNK_BASE,)
219 man_help=(
220 'Set the top-level path to use for trunk in the Subversion '
221 'repository. The default is \\fI%s\\fR.'
222 % (config.DEFAULT_TRUNK_BASE,)
224 metavar='PATH',
226 self.parser.set_default('branches_base', config.DEFAULT_BRANCHES_BASE)
227 group.add_option(IncompatibleOption(
228 '--branches', type='string',
229 action='store', dest='branches_base',
230 help=(
231 'path for branches (default: %s)'
232 % (config.DEFAULT_BRANCHES_BASE,)
234 man_help=(
235 'Set the top-level path to use for branches in the Subversion '
236 'repository. The default is \\fI%s\\fR.'
237 % (config.DEFAULT_BRANCHES_BASE,)
239 metavar='PATH',
241 self.parser.set_default('tags_base', config.DEFAULT_TAGS_BASE)
242 group.add_option(IncompatibleOption(
243 '--tags', type='string',
244 action='store', dest='tags_base',
245 help=(
246 'path for tags (default: %s)'
247 % (config.DEFAULT_TAGS_BASE,)
249 man_help=(
250 'Set the top-level path to use for tags in the Subversion '
251 'repository. The default is \\fI%s\\fR.'
252 % (config.DEFAULT_TAGS_BASE,)
254 metavar='PATH',
256 group.add_option(ContextOption(
257 '--no-prune',
258 action='store_false', dest='prune',
259 help='don\'t prune empty directories',
260 man_help=(
261 'When all files are deleted from a directory in the Subversion '
262 'repository, don\'t delete the empty directory (the default is '
263 'to delete any empty directories).'
266 group.add_option(ContextOption(
267 '--no-cross-branch-commits',
268 action='store_false', dest='cross_branch_commits',
269 help='prevent the creation of cross-branch commits',
270 man_help=(
271 'Prevent the creation of commits that affect files on multiple '
272 'branches at once.'
276 return group
278 def _get_extraction_options_group(self):
279 group = RunOptions._get_extraction_options_group(self)
281 self.parser.set_default('use_internal_co', False)
282 group.add_option(IncompatibleOption(
283 '--use-internal-co',
284 action='store_true',
285 help=(
286 'use internal code to extract revision contents '
287 '(fastest but disk space intensive) (default)'
289 man_help=(
290 'Use internal code to extract revision contents. This '
291 'is up to 50% faster than using \\fB--use-rcs\\fR, but needs '
292 'a lot of disk space: roughly the size of your CVS repository '
293 'plus the peak size of a complete checkout of the repository '
294 'with all branches that existed and still had commits pending '
295 'at a given time. This option is the default.'
298 self.parser.set_default('use_cvs', False)
299 group.add_option(IncompatibleOption(
300 '--use-cvs',
301 action='store_true',
302 help=(
303 'use CVS to extract revision contents (slower than '
304 '--use-internal-co or --use-rcs)'
306 man_help=(
307 'Use CVS to extract revision contents. This option is slower '
308 'than \\fB--use-internal-co\\fR or \\fB--use-rcs\\fR.'
311 self.parser.set_default('use_rcs', False)
312 group.add_option(IncompatibleOption(
313 '--use-rcs',
314 action='store_true',
315 help=(
316 'use RCS to extract revision contents (faster than '
317 '--use-cvs but fails in some cases)'
319 man_help=(
320 'Use RCS \'co\' to extract revision contents. This option is '
321 'faster than \\fB--use-cvs\\fR but fails in some cases.'
325 return group
327 def _get_environment_options_group(self):
328 group = RunOptions._get_environment_options_group(self)
330 group.add_option(ContextOption(
331 '--svnadmin', type='string',
332 action='store', dest='svnadmin_executable',
333 help='path to the "svnadmin" program',
334 man_help=(
335 'Path to the \\fIsvnadmin\\fR program. (\\fIsvnadmin\\fR is '
336 'needed when the \\fB-s\\fR/\\fB--svnrepos\\fR output option is '
337 'used.)'
339 metavar='PATH',
342 return group
344 def callback_dump_only(self, option, opt_str, value, parser):
345 parser.values.dump_only = True
346 Log().error(
347 warning_prefix +
348 ': The --dump-only option is deprecated (it is implied '
349 'by --dumpfile).\n'
352 def callback_create(self, option, opt_str, value, parser):
353 Log().error(
354 warning_prefix +
355 ': The behaviour produced by the --create option is now the '
356 'default;\n'
357 'passing the option is deprecated.\n'
360 def callback_manpage(self, option, opt_str, value, parser):
361 f = codecs.getwriter('utf_8')(sys.stdout)
362 ManWriter(
363 parser,
364 section='1',
365 date=datetime.date.today(),
366 source='Version %s' % (VERSION,),
367 manual='User Commands',
368 short_desc=short_desc,
369 synopsis=synopsis,
370 long_desc=long_desc,
371 files=files,
372 authors=authors,
373 see_also=see_also,
374 ).write_manpage(f)
375 sys.exit(0)
377 def process_extraction_options(self):
378 """Process options related to extracting data from the CVS repository."""
380 ctx = Ctx()
381 options = self.options
383 not_both(options.use_rcs, '--use-rcs',
384 options.use_cvs, '--use-cvs')
386 not_both(options.use_rcs, '--use-rcs',
387 options.use_internal_co, '--use-internal-co')
389 not_both(options.use_cvs, '--use-cvs',
390 options.use_internal_co, '--use-internal-co')
392 if options.use_rcs:
393 ctx.revision_recorder = NullRevisionRecorder()
394 ctx.revision_excluder = NullRevisionExcluder()
395 ctx.revision_reader = RCSRevisionReader(options.co_executable)
396 elif options.use_cvs:
397 ctx.revision_recorder = NullRevisionRecorder()
398 ctx.revision_excluder = NullRevisionExcluder()
399 ctx.revision_reader = CVSRevisionReader(options.cvs_executable)
400 else:
401 # --use-internal-co is the default:
402 ctx.revision_recorder = InternalRevisionRecorder(compress=True)
403 ctx.revision_excluder = InternalRevisionExcluder()
404 ctx.revision_reader = InternalRevisionReader(compress=True)
406 def process_output_options(self):
407 """Process the options related to SVN output."""
409 ctx = Ctx()
410 options = self.options
412 if options.dump_only and not options.dumpfile:
413 raise FatalError("'--dump-only' requires '--dumpfile' to be specified.")
415 if not options.svnrepos and not options.dumpfile and not ctx.dry_run:
416 raise FatalError("must pass one of '-s' or '--dumpfile'.")
418 not_both(options.svnrepos, '-s',
419 options.dumpfile, '--dumpfile')
421 not_both(options.dumpfile, '--dumpfile',
422 options.existing_svnrepos, '--existing-svnrepos')
424 not_both(options.bdb_txn_nosync, '--bdb-txn-nosync',
425 options.existing_svnrepos, '--existing-svnrepos')
427 not_both(options.dumpfile, '--dumpfile',
428 options.bdb_txn_nosync, '--bdb-txn-nosync')
430 not_both(options.fs_type, '--fs-type',
431 options.existing_svnrepos, '--existing-svnrepos')
433 if (
434 options.fs_type
435 and options.fs_type != 'bdb'
436 and options.bdb_txn_nosync
438 raise FatalError("cannot pass --bdb-txn-nosync with --fs-type=%s."
439 % options.fs_type)
441 if options.svnrepos:
442 if options.existing_svnrepos:
443 ctx.output_option = ExistingRepositoryOutputOption(options.svnrepos)
444 else:
445 ctx.output_option = NewRepositoryOutputOption(
446 options.svnrepos,
447 fs_type=options.fs_type, bdb_txn_nosync=options.bdb_txn_nosync,
448 create_options=options.create_options)
449 else:
450 ctx.output_option = DumpfileOutputOption(options.dumpfile)
452 def add_project(
453 self,
454 project_cvs_repos_path,
455 trunk_path=None, branches_path=None, tags_path=None,
456 initial_directories=[],
457 symbol_transforms=None,
458 symbol_strategy_rules=[],
460 """Add a project to be converted.
462 Most arguments are passed straight through to the Project
463 constructor. SYMBOL_STRATEGY_RULES is an iterable of
464 SymbolStrategyRules that will be applied to symbols in this
465 project."""
467 if trunk_path is not None:
468 trunk_path = normalize_svn_path(trunk_path, allow_empty=True)
469 if branches_path is not None:
470 branches_path = normalize_svn_path(branches_path, allow_empty=False)
471 if tags_path is not None:
472 tags_path = normalize_svn_path(tags_path, allow_empty=False)
474 initial_directories = [
475 path
476 for path in [trunk_path, branches_path, tags_path]
477 if path
478 ] + [
479 normalize_svn_path(path)
480 for path in initial_directories
483 symbol_strategy_rules = list(symbol_strategy_rules)
485 # Add rules to set the SVN paths for LODs depending on whether
486 # they are the trunk, tags, or branches:
487 if trunk_path is not None:
488 symbol_strategy_rules.append(TrunkPathRule(trunk_path))
489 if branches_path is not None:
490 symbol_strategy_rules.append(BranchesPathRule(branches_path))
491 if tags_path is not None:
492 symbol_strategy_rules.append(TagsPathRule(tags_path))
494 id = len(self.projects)
495 project = Project(
497 project_cvs_repos_path,
498 initial_directories=initial_directories,
499 symbol_transforms=symbol_transforms,
502 self.projects.append(project)
503 self.project_symbol_strategy_rules.append(symbol_strategy_rules)
505 def clear_projects(self):
506 """Clear the list of projects to be converted.
508 This method is for the convenience of options files, which may
509 want to import one another."""
511 del self.projects[:]
512 del self.project_symbol_strategy_rules[:]
514 def process_options(self):
515 # Consistency check for options and arguments.
516 if len(self.args) == 0:
517 self.usage()
518 sys.exit(1)
520 if len(self.args) > 1:
521 Log().error(error_prefix + ": must pass only one CVS repository.\n")
522 self.usage()
523 sys.exit(1)
525 cvsroot = self.args[0]
527 self.process_extraction_options()
528 self.process_output_options()
529 self.process_symbol_strategy_options()
530 self.process_property_setter_options()
532 # Create the default project (using ctx.trunk, ctx.branches, and
533 # ctx.tags):
534 self.add_project(
535 cvsroot,
536 trunk_path=self.options.trunk_base,
537 branches_path=self.options.branches_base,
538 tags_path=self.options.tags_base,
539 symbol_transforms=self.options.symbol_transforms,
540 symbol_strategy_rules=self.options.symbol_strategy_rules,