Push RunOptions.description down to derived classes.
[cvs2svn.git] / cvs2svn_lib / run_options.py
blobe04255045fe31699a5e6041a4738bd890ed73322
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 contains classes to set common cvs2xxx run options."""
19 import sys
20 import re
21 import optparse
22 from optparse import OptionGroup
23 import datetime
24 import tempfile
25 import codecs
26 import time
28 from cvs2svn_lib.version import VERSION
29 from cvs2svn_lib import config
30 from cvs2svn_lib.common import error_prefix
31 from cvs2svn_lib.common import FatalError
32 from cvs2svn_lib.man_writer import ManWriter
33 from cvs2svn_lib.log import logger
34 from cvs2svn_lib.context import Ctx
35 from cvs2svn_lib.man_writer import ManOption
36 from cvs2svn_lib.pass_manager import InvalidPassError
37 from cvs2svn_lib.revision_manager import NullRevisionCollector
38 from cvs2svn_lib.rcs_revision_manager import RCSRevisionReader
39 from cvs2svn_lib.cvs_revision_manager import CVSRevisionReader
40 from cvs2svn_lib.checkout_internal import InternalRevisionCollector
41 from cvs2svn_lib.checkout_internal import InternalRevisionReader
42 from cvs2svn_lib.symbol_strategy import AllBranchRule
43 from cvs2svn_lib.symbol_strategy import AllExcludedRule
44 from cvs2svn_lib.symbol_strategy import AllTagRule
45 from cvs2svn_lib.symbol_strategy import BranchIfCommitsRule
46 from cvs2svn_lib.symbol_strategy import ExcludeRegexpStrategyRule
47 from cvs2svn_lib.symbol_strategy import ForceBranchRegexpStrategyRule
48 from cvs2svn_lib.symbol_strategy import ForceTagRegexpStrategyRule
49 from cvs2svn_lib.symbol_strategy import ExcludeTrivialImportBranchRule
50 from cvs2svn_lib.symbol_strategy import HeuristicStrategyRule
51 from cvs2svn_lib.symbol_strategy import UnambiguousUsageRule
52 from cvs2svn_lib.symbol_strategy import HeuristicPreferredParentRule
53 from cvs2svn_lib.symbol_strategy import SymbolHintsFileRule
54 from cvs2svn_lib.symbol_transform import ReplaceSubstringsSymbolTransform
55 from cvs2svn_lib.symbol_transform import RegexpSymbolTransform
56 from cvs2svn_lib.symbol_transform import NormalizePathsSymbolTransform
57 from cvs2svn_lib.property_setters import AutoPropsPropertySetter
58 from cvs2svn_lib.property_setters import CVSBinaryFileDefaultMimeTypeSetter
59 from cvs2svn_lib.property_setters import CVSBinaryFileEOLStyleSetter
60 from cvs2svn_lib.property_setters import CVSRevisionNumberSetter
61 from cvs2svn_lib.property_setters import DefaultEOLStyleSetter
62 from cvs2svn_lib.property_setters import EOLStyleFromMimeTypeSetter
63 from cvs2svn_lib.property_setters import ExecutablePropertySetter
64 from cvs2svn_lib.property_setters import DescriptionPropertySetter
65 from cvs2svn_lib.property_setters import KeywordsPropertySetter
66 from cvs2svn_lib.property_setters import MimeMapper
67 from cvs2svn_lib.property_setters import SVNBinaryFileKeywordsPropertySetter
70 usage = """\
71 Usage: %prog --options OPTIONFILE
72 %prog [OPTION...] OUTPUT-OPTION CVS-REPOS-PATH"""
75 class IncompatibleOption(ManOption):
76 """A ManOption that is incompatible with the --options option.
78 Record that the option was used so that error checking can later be
79 done."""
81 def __init__(self, *args, **kw):
82 ManOption.__init__(self, *args, **kw)
84 def take_action(self, action, dest, opt, value, values, parser):
85 oio = parser.values.options_incompatible_options
86 if opt not in oio:
87 oio.append(opt)
88 return ManOption.take_action(
89 self, action, dest, opt, value, values, parser
93 class ContextOption(ManOption):
94 """A ManOption that stores its value to Ctx."""
96 def __init__(self, *args, **kw):
97 if kw.get('action') not in self.STORE_ACTIONS:
98 raise ValueError('Invalid action: %s' % (kw['action'],))
100 self.__compatible_with_option = kw.pop('compatible_with_option', False)
101 self.__action = kw.pop('action')
102 try:
103 self.__dest = kw.pop('dest')
104 except KeyError:
105 opt = args[0]
106 if not opt.startswith('--'):
107 raise ValueError()
108 self.__dest = opt[2:].replace('-', '_')
109 if 'const' in kw:
110 self.__const = kw.pop('const')
112 kw['action'] = 'callback'
113 kw['callback'] = self.__callback
115 ManOption.__init__(self, *args, **kw)
117 def __callback(self, option, opt_str, value, parser):
118 if not self.__compatible_with_option:
119 oio = parser.values.options_incompatible_options
120 if opt_str not in oio:
121 oio.append(opt_str)
123 action = self.__action
124 dest = self.__dest
126 if action == "store":
127 setattr(Ctx(), dest, value)
128 elif action == "store_const":
129 setattr(Ctx(), dest, self.__const)
130 elif action == "store_true":
131 setattr(Ctx(), dest, True)
132 elif action == "store_false":
133 setattr(Ctx(), dest, False)
134 elif action == "append":
135 getattr(Ctx(), dest).append(value)
136 elif action == "count":
137 setattr(Ctx(), dest, getattr(Ctx(), dest, 0) + 1)
138 else:
139 raise RuntimeError("unknown action %r" % self.__action)
141 return 1
144 class IncompatibleOptionsException(FatalError):
145 pass
148 # Options that are not allowed to be used with --trunk-only:
149 SYMBOL_OPTIONS = [
150 '--symbol-transform',
151 '--symbol-hints',
152 '--force-branch',
153 '--force-tag',
154 '--exclude',
155 '--keep-trivial-imports',
156 '--symbol-default',
157 '--no-cross-branch-commits',
160 class SymbolOptionsWithTrunkOnlyException(IncompatibleOptionsException):
161 def __init__(self):
162 IncompatibleOptionsException.__init__(
163 self,
164 'The following symbol-related options cannot be used together\n'
165 'with --trunk-only:\n'
166 ' %s'
167 % ('\n '.join(SYMBOL_OPTIONS),)
171 def not_both(opt1val, opt1name, opt2val, opt2name):
172 """Raise an exception if both opt1val and opt2val are set."""
173 if opt1val and opt2val:
174 raise IncompatibleOptionsException(
175 "cannot pass both '%s' and '%s'." % (opt1name, opt2name,)
179 class RunOptions(object):
180 """A place to store meta-options that are used to start the conversion."""
182 # Components of the man page. Attributes set to None here must be set
183 # by subclasses; others may be overridden/augmented by subclasses if
184 # they wish.
185 short_desc = None
186 synopsis = None
187 long_desc = None
188 files = None
189 authors = [
190 u"C. Michael Pilato <cmpilato@collab.net>",
191 u"Greg Stein <gstein@lyra.org>",
192 u"Branko \u010cibej <brane@xbc.nu>",
193 u"Blair Zajac <blair@orcaware.com>",
194 u"Max Bowsher <maxb@ukf.net>",
195 u"Brian Fitzpatrick <fitz@red-bean.com>",
196 u"Tobias Ringstr\u00f6m <tobias@ringstrom.mine.nu>",
197 u"Karl Fogel <kfogel@collab.net>",
198 u"Erik H\u00fclsmann <e.huelsmann@gmx.net>",
199 u"David Summers <david@summersoft.fay.ar.us>",
200 u"Michael Haggerty <mhagger@alum.mit.edu>",
202 see_also = None
204 def __init__(self, progname, cmd_args, pass_manager):
205 """Process the command-line options, storing run options to SELF.
207 PROGNAME is the name of the program, used in the usage string.
208 CMD_ARGS is the list of command-line arguments passed to the
209 program. PASS_MANAGER is an instance of PassManager, needed to
210 help process the -p and --help-passes options."""
212 self.progname = progname
213 self.cmd_args = cmd_args
214 self.pass_manager = pass_manager
215 self.start_pass = 1
216 self.end_pass = self.pass_manager.num_passes
217 self.profiling = False
219 self.projects = []
221 # A list of one list of SymbolStrategyRules for each project:
222 self.project_symbol_strategy_rules = []
224 parser = self.parser = optparse.OptionParser(
225 usage=usage,
226 description=self.get_description(),
227 add_help_option=False,
229 # A place to record any options used that are incompatible with
230 # --options:
231 parser.set_default('options_incompatible_options', [])
233 # Populate the options parser with the options, one group at a
234 # time:
235 parser.add_option_group(self._get_options_file_options_group())
236 parser.add_option_group(self._get_output_options_group())
237 parser.add_option_group(self._get_conversion_options_group())
238 parser.add_option_group(self._get_symbol_handling_options_group())
239 parser.add_option_group(self._get_subversion_properties_options_group())
240 parser.add_option_group(self._get_extraction_options_group())
241 parser.add_option_group(self._get_environment_options_group())
242 parser.add_option_group(self._get_partial_conversion_options_group())
243 parser.add_option_group(self._get_information_options_group())
245 (self.options, self.args) = parser.parse_args(args=self.cmd_args)
247 # Now the log level has been set; log the time when the run started:
248 logger.verbose(
249 time.strftime(
250 'Conversion start time: %Y-%m-%d %I:%M:%S %Z',
251 time.localtime(logger.start_time)
255 if self.options.options_file_found:
256 # Check that no options that are incompatible with --options
257 # were used:
258 self.verify_option_compatibility()
259 else:
260 # --options was not specified. So do the main initialization
261 # based on other command-line options:
262 self.process_options()
264 # Check for problems with the options:
265 self.check_options()
267 @classmethod
268 def get_description(klass):
269 return klass.description
271 def _get_options_file_options_group(self):
272 group = OptionGroup(
273 self.parser, 'Configuration via options file'
275 self.parser.set_default('options_file_found', False)
276 group.add_option(ManOption(
277 '--options', type='string',
278 action='callback', callback=self.callback_options,
279 help=(
280 'read the conversion options from PATH. This '
281 'method allows more flexibility than using '
282 'command-line options. See documentation for info'
284 man_help=(
285 'Read the conversion options from \\fIpath\\fR instead of from '
286 'the command line. This option allows far more conversion '
287 'flexibility than can be achieved using the command-line alone. '
288 'See the documentation for more information. Only the following '
289 'command-line options are allowed in combination with '
290 '\\fB--options\\fR: \\fB-h\\fR/\\fB--help\\fR, '
291 '\\fB--help-passes\\fR, \\fB--version\\fR, '
292 '\\fB-v\\fR/\\fB--verbose\\fR, \\fB-q\\fR/\\fB--quiet\\fR, '
293 '\\fB-p\\fR/\\fB--pass\\fR/\\fB--passes\\fR, \\fB--dry-run\\fR, '
294 '\\fB--profile\\fR, \\fB--trunk-only\\fR, \\fB--encoding\\fR, '
295 'and \\fB--fallback-encoding\\fR. '
296 'Options are processed in the order specified on the command '
297 'line.'
299 metavar='PATH',
301 return group
303 def _get_output_options_group(self):
304 group = OptionGroup(self.parser, 'Output options')
305 return group
307 def _get_conversion_options_group(self):
308 group = OptionGroup(self.parser, 'Conversion options')
309 group.add_option(ContextOption(
310 '--trunk-only',
311 action='store_true',
312 compatible_with_option=True,
313 help='convert only trunk commits, not tags nor branches',
314 man_help=(
315 'Convert only trunk commits, not tags nor branches.'
318 group.add_option(ManOption(
319 '--encoding', type='string',
320 action='callback', callback=self.callback_encoding,
321 help=(
322 'encoding for paths and log messages in CVS repos. '
323 'If option is specified multiple times, encoders '
324 'are tried in order until one succeeds. See '
325 'http://docs.python.org/lib/standard-encodings.html '
326 'for a list of standard Python encodings.'
328 man_help=(
329 'Use \\fIencoding\\fR as the encoding for filenames, log '
330 'messages, and author names in the CVS repos. This option '
331 'may be specified multiple times, in which case the encodings '
332 'are tried in order until one succeeds. Default: ascii. See '
333 'http://docs.python.org/lib/standard-encodings.html for a list '
334 'of other standard encodings.'
336 metavar='ENC',
338 group.add_option(ManOption(
339 '--fallback-encoding', type='string',
340 action='callback', callback=self.callback_fallback_encoding,
341 help='If all --encodings fail, use lossy encoding with ENC',
342 man_help=(
343 'If none of the encodings specified with \\fB--encoding\\fR '
344 'succeed in decoding an author name or log message, then fall '
345 'back to using \\fIencoding\\fR in lossy \'replace\' mode. '
346 'Use of this option may cause information to be lost, but at '
347 'least it allows the conversion to run to completion. This '
348 'option only affects the encoding of log messages and author '
349 'names; there is no fallback encoding for filenames. (By '
350 'using an \\fB--options\\fR file, it is possible to specify '
351 'a fallback encoding for filenames.) Default: disabled.'
353 metavar='ENC',
355 group.add_option(ContextOption(
356 '--retain-conflicting-attic-files',
357 action='store_true',
358 help=(
359 'if a file appears both in and out of '
360 'the CVS Attic, then leave the attic version in a '
361 'subdirectory called "Attic"'
363 man_help=(
364 'If a file appears both inside and outside of the CVS attic, '
365 'retain the attic version in a subdirectory called '
366 '\'Attic\'. (Normally this situation is treated as a fatal '
367 'error.)'
371 return group
373 def _get_symbol_handling_options_group(self):
374 group = OptionGroup(self.parser, 'Symbol handling')
375 self.parser.set_default('symbol_transforms', [])
376 group.add_option(IncompatibleOption(
377 '--symbol-transform', type='string',
378 action='callback', callback=self.callback_symbol_transform,
379 help=(
380 'transform symbol names from P to S, where P and S '
381 'use Python regexp and reference syntax '
382 'respectively. P must match the whole symbol name'
384 man_help=(
385 'Transform RCS/CVS symbol names before entering them into the '
386 'output history. \\fIpattern\\fR is a Python regexp pattern that '
387 'is matches against the entire symbol name; \\fIreplacement\\fR '
388 'is a replacement using Python\'s regexp reference syntax. '
389 'You may specify any number of these options; they will be '
390 'applied in the order given on the command line.'
392 metavar='P:S',
394 self.parser.set_default('symbol_strategy_rules', [])
395 group.add_option(IncompatibleOption(
396 '--symbol-hints', type='string',
397 action='callback', callback=self.callback_symbol_hints,
398 help='read symbol conversion hints from PATH',
399 man_help=(
400 'Read symbol conversion hints from \\fIpath\\fR. The format of '
401 '\\fIpath\\fR is the same as the format output by '
402 '\\fB--write-symbol-info\\fR, namely a text file with four '
403 'whitespace-separated columns: \\fIproject-id\\fR, '
404 '\\fIsymbol\\fR, \\fIconversion\\fR, and '
405 '\\fIparent-lod-name\\fR. \\fIproject-id\\fR is the numerical '
406 'ID of the project to which the symbol belongs, counting from '
407 '0. \\fIproject-id\\fR can be set to \'.\' if '
408 'project-specificity is not needed. \\fIsymbol-name\\fR is the '
409 'name of the symbol being specified. \\fIconversion\\fR '
410 'specifies how the symbol should be converted, and can be one '
411 'of the values \'branch\', \'tag\', or \'exclude\'. If '
412 '\\fIconversion\\fR is \'.\', then this rule does not affect '
413 'how the symbol is converted. \\fIparent-lod-name\\fR is the '
414 'name of the symbol from which this symbol should sprout, or '
415 '\'.trunk.\' if the symbol should sprout from trunk. If '
416 '\\fIparent-lod-name\\fR is omitted or \'.\', then this rule '
417 'does not affect the preferred parent of this symbol. The file '
418 'may contain blank lines or comment lines (lines whose first '
419 'non-whitespace character is \'#\').'
421 metavar='PATH',
423 self.parser.set_default('symbol_default', 'heuristic')
424 group.add_option(IncompatibleOption(
425 '--symbol-default', type='choice',
426 choices=['heuristic', 'strict', 'branch', 'tag', 'exclude'],
427 action='store',
428 help=(
429 'specify how ambiguous symbols are converted. '
430 'OPT is "heuristic" (default), "strict", "branch", '
431 '"tag" or "exclude"'
433 man_help=(
434 'Specify how to convert ambiguous symbols (those that appear in '
435 'the CVS archive as both branches and tags). \\fIopt\\fR must '
436 'be \'heuristic\' (decide how to treat each ambiguous symbol '
437 'based on whether it was used more often as a branch/tag in '
438 'CVS), \'strict\' (no default; every ambiguous symbol has to be '
439 'resolved manually using \\fB--force-branch\\fR, '
440 '\\fB--force-tag\\fR, or \\fB--exclude\\fR), \'branch\' (treat '
441 'every ambiguous symbol as a branch), \'tag\' (treat every '
442 'ambiguous symbol as a tag), or \'exclude\' (do not convert '
443 'ambiguous symbols). The default is \'heuristic\'.'
445 metavar='OPT',
447 group.add_option(IncompatibleOption(
448 '--force-branch', type='string',
449 action='callback', callback=self.callback_force_branch,
450 help='force symbols matching REGEXP to be branches',
451 man_help=(
452 'Force symbols whose names match \\fIregexp\\fR to be branches. '
453 '\\fIregexp\\fR must match the whole symbol name.'
455 metavar='REGEXP',
457 group.add_option(IncompatibleOption(
458 '--force-tag', type='string',
459 action='callback', callback=self.callback_force_tag,
460 help='force symbols matching REGEXP to be tags',
461 man_help=(
462 'Force symbols whose names match \\fIregexp\\fR to be tags. '
463 '\\fIregexp\\fR must match the whole symbol name.'
465 metavar='REGEXP',
467 group.add_option(IncompatibleOption(
468 '--exclude', type='string',
469 action='callback', callback=self.callback_exclude,
470 help='exclude branches and tags matching REGEXP',
471 man_help=(
472 'Exclude branches and tags whose names match \\fIregexp\\fR '
473 'from the conversion. \\fIregexp\\fR must match the whole '
474 'symbol name.'
476 metavar='REGEXP',
478 self.parser.set_default('keep_trivial_imports', False)
479 group.add_option(IncompatibleOption(
480 '--keep-trivial-imports',
481 action='store_true',
482 help=(
483 'do not exclude branches that were only used for '
484 'a single import (usually these are unneeded)'
486 man_help=(
487 'Do not exclude branches that were only used for a single '
488 'import. (By default such branches are excluded because they '
489 'are usually created by the inappropriate use of \\fBcvs '
490 'import\\fR.)'
494 return group
496 def _get_subversion_properties_options_group(self):
497 group = OptionGroup(self.parser, 'Subversion properties')
499 if self.DEFAULT_USERNAME is None:
500 default = 'The default is to use no author at all for such commits.'
501 else:
502 default = 'Default: "%s".' % (self.DEFAULT_USERNAME,)
504 group.add_option(ContextOption(
505 '--username', type='string', default=self.DEFAULT_USERNAME,
506 action='store',
507 help='username for synthesized commits. ' + default,
508 man_help=(
509 'Set the default username to \\fIname\\fR when this program needs '
510 'to generate a commit for which CVS does not record the '
511 'original username. This happens when a branch or tag is '
512 'created. '
513 + default
515 metavar='NAME',
517 self.parser.set_default('auto_props_files', [])
518 group.add_option(IncompatibleOption(
519 '--auto-props', type='string',
520 action='append', dest='auto_props_files',
521 help=(
522 'set file properties from the auto-props section '
523 'of a file in svn config format'
525 man_help=(
526 'Specify a file in the format of Subversion\'s config file, '
527 'whose [auto-props] section can be used to set arbitrary '
528 'properties on files in the Subversion repository based on '
529 'their filenames. (The [auto-props] section header must be '
530 'present; other sections of the config file, including the '
531 'enable-auto-props setting, are ignored.) Filenames are matched '
532 'to the filename patterns case-insensitively.'
535 metavar='FILE',
537 self.parser.set_default('mime_types_files', [])
538 group.add_option(IncompatibleOption(
539 '--mime-types', type='string',
540 action='append', dest='mime_types_files',
541 help=(
542 'specify an apache-style mime.types file for setting '
543 'svn:mime-type'
545 man_help=(
546 'Specify an apache-style mime.types \\fIfile\\fR for setting '
547 'svn:mime-type.'
549 metavar='FILE',
551 self.parser.set_default('eol_from_mime_type', False)
552 group.add_option(IncompatibleOption(
553 '--eol-from-mime-type',
554 action='store_true',
555 help='set svn:eol-style from mime type if known',
556 man_help=(
557 'For files that don\'t have the kb expansion mode but have a '
558 'known mime type, set the eol-style based on the mime type. '
559 'For such files, set svn:eol-style to "native" if the mime type '
560 'begins with "text/", and leave it unset (i.e., no EOL '
561 'translation) otherwise. Files with unknown mime types are '
562 'not affected by this option. This option has no effect '
563 'unless the \\fB--mime-types\\fR option is also specified.'
566 self.parser.set_default('default_eol', 'binary')
567 group.add_option(IncompatibleOption(
568 '--default-eol', type='choice',
569 choices=['binary', 'native', 'CRLF', 'LF', 'CR'],
570 action='store',
571 help=(
572 'default svn:eol-style for non-binary files with '
573 'undetermined mime types. STYLE is "binary" '
574 '(default), "native", "CRLF", "LF", or "CR"'
576 man_help=(
577 'Set svn:eol-style to \\fIstyle\\fR for files that don\'t have '
578 'the CVS \'kb\' expansion mode and whose end-of-line '
579 'translation mode hasn\'t been determined by one of the other '
580 'options. \\fIstyle\\fR must be \'binary\' (default), '
581 '\'native\', \'CRLF\', \'LF\', or \'CR\'.'
583 metavar='STYLE',
585 self.parser.set_default('keywords_off', False)
586 group.add_option(IncompatibleOption(
587 '--keywords-off',
588 action='store_true',
589 help=(
590 'don\'t set svn:keywords on any files (by default, '
591 'cvs2svn sets svn:keywords on non-binary files to "%s")'
592 % (config.SVN_KEYWORDS_VALUE,)
594 man_help=(
595 'By default, cvs2svn sets svn:keywords on CVS files to "author '
596 'id date" if the mode of the RCS file in question is either kv, '
597 'kvl or unset. If you use the --keywords-off switch, cvs2svn '
598 'will not set svn:keywords for any file. While this will not '
599 'touch the keywords in the contents of your files, Subversion '
600 'will not expand them.'
603 group.add_option(ContextOption(
604 '--keep-cvsignore',
605 action='store_true',
606 help=(
607 'keep .cvsignore files (in addition to creating '
608 'the analogous svn:ignore properties)'
610 man_help=(
611 'Include \\fI.cvsignore\\fR files in the output. (Normally '
612 'they are unneeded because cvs2svn sets the corresponding '
613 '\\fIsvn:ignore\\fR properties.)'
616 group.add_option(IncompatibleOption(
617 '--cvs-revnums',
618 action='callback', callback=self.callback_cvs_revnums,
619 help='record CVS revision numbers as file properties',
620 man_help=(
621 'Record CVS revision numbers as file properties in the '
622 'Subversion repository. (Note that unless it is removed '
623 'explicitly, the last CVS revision number will remain '
624 'associated with the file even after the file is changed '
625 'within Subversion.)'
629 # Deprecated options:
630 group.add_option(IncompatibleOption(
631 '--no-default-eol',
632 action='store_const', dest='default_eol', const=None,
633 help=optparse.SUPPRESS_HELP,
634 man_help=optparse.SUPPRESS_HELP,
636 self.parser.set_default('auto_props_ignore_case', True)
637 # True is the default now, so this option has no effect:
638 group.add_option(IncompatibleOption(
639 '--auto-props-ignore-case',
640 action='store_true',
641 help=optparse.SUPPRESS_HELP,
642 man_help=optparse.SUPPRESS_HELP,
645 return group
647 def _get_extraction_options_group(self):
648 group = OptionGroup(self.parser, 'Extraction options')
650 return group
652 def _add_use_internal_co_option(self, group):
653 self.parser.set_default('use_internal_co', False)
654 group.add_option(IncompatibleOption(
655 '--use-internal-co',
656 action='store_true',
657 help=(
658 'use internal code to extract revision contents '
659 '(fastest but disk space intensive) (default)'
661 man_help=(
662 'Use internal code to extract revision contents. This '
663 'is up to 50% faster than using \\fB--use-rcs\\fR, but needs '
664 'a lot of disk space: roughly the size of your CVS repository '
665 'plus the peak size of a complete checkout of the repository '
666 'with all branches that existed and still had commits pending '
667 'at a given time. This option is the default.'
671 def _add_use_cvs_option(self, group):
672 self.parser.set_default('use_cvs', False)
673 group.add_option(IncompatibleOption(
674 '--use-cvs',
675 action='store_true',
676 help=(
677 'use CVS to extract revision contents (slower than '
678 '--use-internal-co or --use-rcs)'
680 man_help=(
681 'Use CVS to extract revision contents. This option is slower '
682 'than \\fB--use-internal-co\\fR or \\fB--use-rcs\\fR.'
686 def _add_use_rcs_option(self, group):
687 self.parser.set_default('use_rcs', False)
688 group.add_option(IncompatibleOption(
689 '--use-rcs',
690 action='store_true',
691 help=(
692 'use RCS to extract revision contents (faster than '
693 '--use-cvs but fails in some cases)'
695 man_help=(
696 'Use RCS \'co\' to extract revision contents. This option is '
697 'faster than \\fB--use-cvs\\fR but fails in some cases.'
701 def _get_environment_options_group(self):
702 group = OptionGroup(self.parser, 'Environment options')
703 group.add_option(ContextOption(
704 '--tmpdir', type='string',
705 action='store',
706 help=(
707 'directory to use for temporary data files '
708 '(default is to create a temporary subdirectory under %r)'
709 ) % (tempfile.gettempdir(),),
710 man_help=(
711 'Set the \\fIpath\\fR to use for temporary data. The default '
712 'is to create a temporary subdirectory under \\fI%s\\fR.'
713 ) % (tempfile.gettempdir(),),
714 metavar='PATH',
716 self.parser.set_default('co_executable', config.CO_EXECUTABLE)
717 group.add_option(IncompatibleOption(
718 '--co', type='string',
719 action='store', dest='co_executable',
720 help='path to the "co" program (required if --use-rcs)',
721 man_help=(
722 'Path to the \\fIco\\fR program. (\\fIco\\fR is needed if the '
723 '\\fB--use-rcs\\fR option is used.)'
725 metavar='PATH',
727 self.parser.set_default('cvs_executable', config.CVS_EXECUTABLE)
728 group.add_option(IncompatibleOption(
729 '--cvs', type='string',
730 action='store', dest='cvs_executable',
731 help='path to the "cvs" program (required if --use-cvs)',
732 man_help=(
733 'Path to the \\fIcvs\\fR program. (\\fIcvs\\fR is needed if the '
734 '\\fB--use-cvs\\fR option is used.)'
736 metavar='PATH',
739 return group
741 def _get_partial_conversion_options_group(self):
742 group = OptionGroup(self.parser, 'Partial conversions')
743 group.add_option(ManOption(
744 '--pass', type='string',
745 action='callback', callback=self.callback_passes,
746 help='execute only specified PASS of conversion',
747 man_help=(
748 'Execute only pass \\fIpass\\fR of the conversion. '
749 '\\fIpass\\fR can be specified by name or by number (see '
750 '\\fB--help-passes\\fR).'
752 metavar='PASS',
754 group.add_option(ManOption(
755 '--passes', '-p', type='string',
756 action='callback', callback=self.callback_passes,
757 help=(
758 'execute passes START through END, inclusive (PASS, '
759 'START, and END can be pass names or numbers)'
761 man_help=(
762 'Execute passes \\fIstart\\fR through \\fIend\\fR of the '
763 'conversion (inclusive). \\fIstart\\fR and \\fIend\\fR can be '
764 'specified by name or by number (see \\fB--help-passes\\fR). '
765 'If \\fIstart\\fR or \\fIend\\fR is missing, it defaults to '
766 'the first or last pass, respectively. For this to work the '
767 'earlier passes must have been completed before on the '
768 'same CVS repository, and the generated data files must be '
769 'in the temporary directory (see \\fB--tmpdir\\fR).'
771 metavar='[START]:[END]',
774 return group
776 def _get_information_options_group(self):
777 group = OptionGroup(self.parser, 'Information options')
778 group.add_option(ManOption(
779 '--version',
780 action='callback', callback=self.callback_version,
781 help='print the version number',
782 man_help='Print the version number.',
784 group.add_option(ManOption(
785 '--help', '-h',
786 action="help",
787 help='print this usage message and exit with success',
788 man_help='Print the usage message and exit with success.',
790 group.add_option(ManOption(
791 '--help-passes',
792 action='callback', callback=self.callback_help_passes,
793 help='list the available passes and their numbers',
794 man_help=(
795 'Print the numbers and names of the conversion passes and '
796 'exit with success.'
799 group.add_option(ManOption(
800 '--man',
801 action='callback', callback=self.callback_manpage,
802 help='write the manpage for this program to standard output',
803 man_help=(
804 'Output the unix-style manpage for this program to standard '
805 'output.'
808 group.add_option(ManOption(
809 '--verbose', '-v',
810 action='callback', callback=self.callback_verbose,
811 help='verbose (may be specified twice for debug output)',
812 man_help=(
813 'Print more information while running. This option may be '
814 'specified twice to output voluminous debugging information.'
817 group.add_option(ManOption(
818 '--quiet', '-q',
819 action='callback', callback=self.callback_quiet,
820 help='quiet (may be specified twice for very quiet)',
821 man_help=(
822 'Print less information while running. This option may be '
823 'specified twice to suppress all non-error output.'
826 group.add_option(ContextOption(
827 '--write-symbol-info', type='string',
828 action='store', dest='symbol_info_filename',
829 help='write information and statistics about CVS symbols to PATH.',
830 man_help=(
831 'Write to \\fIpath\\fR symbol statistics and information about '
832 'how symbols were converted during CollateSymbolsPass.'
834 metavar='PATH',
836 group.add_option(ContextOption(
837 '--skip-cleanup',
838 action='store_true',
839 help='prevent the deletion of intermediate files',
840 man_help='Prevent the deletion of temporary files.',
842 prof = 'cProfile'
843 try:
844 import cProfile
845 except ImportError:
846 prof = 'hotshot'
847 group.add_option(ManOption(
848 '--profile',
849 action='callback', callback=self.callback_profile,
850 help='profile with \'' + prof + '\' (into file cvs2svn.' + prof + ')',
851 man_help=(
852 'Profile with \'' + prof + '\' (into file \\fIcvs2svn.' + prof + '\\fR).'
856 return group
858 def callback_options(self, option, opt_str, value, parser):
859 parser.values.options_file_found = True
860 self.process_options_file(value)
862 def callback_encoding(self, option, opt_str, value, parser):
863 ctx = Ctx()
865 try:
866 ctx.cvs_author_decoder.add_encoding(value)
867 ctx.cvs_log_decoder.add_encoding(value)
868 ctx.cvs_filename_decoder.add_encoding(value)
869 except LookupError, e:
870 raise FatalError(str(e))
872 def callback_fallback_encoding(self, option, opt_str, value, parser):
873 ctx = Ctx()
875 try:
876 ctx.cvs_author_decoder.set_fallback_encoding(value)
877 ctx.cvs_log_decoder.set_fallback_encoding(value)
878 # Don't use fallback_encoding for filenames.
879 except LookupError, e:
880 raise FatalError(str(e))
882 def callback_help_passes(self, option, opt_str, value, parser):
883 self.pass_manager.help_passes()
884 sys.exit(0)
886 def callback_manpage(self, option, opt_str, value, parser):
887 f = codecs.getwriter('utf_8')(sys.stdout)
888 writer = ManWriter(parser,
889 section='1',
890 date=datetime.date.today(),
891 source='Version %s' % (VERSION,),
892 manual='User Commands',
893 short_desc=self.short_desc,
894 synopsis=self.synopsis,
895 long_desc=self.long_desc,
896 files=self.files,
897 authors=self.authors,
898 see_also=self.see_also)
899 writer.write_manpage(f)
900 sys.exit(0)
902 def callback_version(self, option, opt_str, value, parser):
903 sys.stdout.write(
904 '%s version %s\n' % (self.progname, VERSION)
906 sys.exit(0)
908 def callback_verbose(self, option, opt_str, value, parser):
909 logger.increase_verbosity()
911 def callback_quiet(self, option, opt_str, value, parser):
912 logger.decrease_verbosity()
914 def callback_passes(self, option, opt_str, value, parser):
915 if value.find(':') >= 0:
916 start_pass, end_pass = value.split(':')
917 self.start_pass = self.pass_manager.get_pass_number(start_pass, 1)
918 self.end_pass = self.pass_manager.get_pass_number(
919 end_pass, self.pass_manager.num_passes
921 else:
922 self.end_pass = \
923 self.start_pass = \
924 self.pass_manager.get_pass_number(value)
926 def callback_profile(self, option, opt_str, value, parser):
927 self.profiling = True
929 def callback_symbol_hints(self, option, opt_str, value, parser):
930 parser.values.symbol_strategy_rules.append(SymbolHintsFileRule(value))
932 def callback_force_branch(self, option, opt_str, value, parser):
933 parser.values.symbol_strategy_rules.append(
934 ForceBranchRegexpStrategyRule(value)
937 def callback_force_tag(self, option, opt_str, value, parser):
938 parser.values.symbol_strategy_rules.append(
939 ForceTagRegexpStrategyRule(value)
942 def callback_exclude(self, option, opt_str, value, parser):
943 parser.values.symbol_strategy_rules.append(
944 ExcludeRegexpStrategyRule(value)
947 def callback_cvs_revnums(self, option, opt_str, value, parser):
948 Ctx().revision_property_setters.append(CVSRevisionNumberSetter())
950 def callback_symbol_transform(self, option, opt_str, value, parser):
951 [pattern, replacement] = value.split(":")
952 try:
953 parser.values.symbol_transforms.append(
954 RegexpSymbolTransform(pattern, replacement)
956 except re.error:
957 raise FatalError("'%s' is not a valid regexp." % (pattern,))
959 # Common to SVNRunOptions, HgRunOptions (GitRunOptions and
960 # BzrRunOptions do not support --use-internal-co, so cannot use this).
961 def process_all_extraction_options(self):
962 ctx = Ctx()
963 options = self.options
965 not_both(options.use_rcs, '--use-rcs',
966 options.use_cvs, '--use-cvs')
968 not_both(options.use_rcs, '--use-rcs',
969 options.use_internal_co, '--use-internal-co')
971 not_both(options.use_cvs, '--use-cvs',
972 options.use_internal_co, '--use-internal-co')
974 if options.use_rcs:
975 ctx.revision_collector = NullRevisionCollector()
976 ctx.revision_reader = RCSRevisionReader(options.co_executable)
977 elif options.use_cvs:
978 ctx.revision_collector = NullRevisionCollector()
979 ctx.revision_reader = CVSRevisionReader(options.cvs_executable)
980 else:
981 # --use-internal-co is the default:
982 ctx.revision_collector = InternalRevisionCollector(compress=True)
983 ctx.revision_reader = InternalRevisionReader(compress=True)
985 def process_symbol_strategy_options(self):
986 """Process symbol strategy-related options."""
988 ctx = Ctx()
989 options = self.options
991 # Add the standard symbol name cleanup rules:
992 self.options.symbol_transforms.extend([
993 ReplaceSubstringsSymbolTransform('\\','/'),
994 # Remove leading, trailing, and repeated slashes:
995 NormalizePathsSymbolTransform(),
998 if ctx.trunk_only:
999 if options.symbol_strategy_rules or options.keep_trivial_imports:
1000 raise SymbolOptionsWithTrunkOnlyException()
1002 else:
1003 if not options.keep_trivial_imports:
1004 options.symbol_strategy_rules.append(ExcludeTrivialImportBranchRule())
1006 options.symbol_strategy_rules.append(UnambiguousUsageRule())
1007 if options.symbol_default == 'strict':
1008 pass
1009 elif options.symbol_default == 'branch':
1010 options.symbol_strategy_rules.append(AllBranchRule())
1011 elif options.symbol_default == 'tag':
1012 options.symbol_strategy_rules.append(AllTagRule())
1013 elif options.symbol_default == 'heuristic':
1014 options.symbol_strategy_rules.append(BranchIfCommitsRule())
1015 options.symbol_strategy_rules.append(HeuristicStrategyRule())
1016 elif options.symbol_default == 'exclude':
1017 options.symbol_strategy_rules.append(AllExcludedRule())
1018 else:
1019 assert False
1021 # Now add a rule whose job it is to pick the preferred parents of
1022 # branches and tags:
1023 options.symbol_strategy_rules.append(HeuristicPreferredParentRule())
1025 def process_property_setter_options(self):
1026 """Process the options that set SVN properties."""
1028 ctx = Ctx()
1029 options = self.options
1031 for value in options.auto_props_files:
1032 ctx.file_property_setters.append(
1033 AutoPropsPropertySetter(value, options.auto_props_ignore_case)
1036 for value in options.mime_types_files:
1037 ctx.file_property_setters.append(MimeMapper(value))
1039 ctx.file_property_setters.append(CVSBinaryFileEOLStyleSetter())
1041 ctx.file_property_setters.append(CVSBinaryFileDefaultMimeTypeSetter())
1043 if options.eol_from_mime_type:
1044 ctx.file_property_setters.append(EOLStyleFromMimeTypeSetter())
1046 ctx.file_property_setters.append(
1047 DefaultEOLStyleSetter(options.default_eol)
1050 ctx.file_property_setters.append(SVNBinaryFileKeywordsPropertySetter())
1052 if not options.keywords_off:
1053 ctx.file_property_setters.append(
1054 KeywordsPropertySetter(config.SVN_KEYWORDS_VALUE)
1057 ctx.file_property_setters.append(ExecutablePropertySetter())
1059 ctx.file_property_setters.append(DescriptionPropertySetter())
1061 def process_options(self):
1062 """Do the main configuration based on command-line options.
1064 This method is only called if the --options option was not
1065 specified."""
1067 raise NotImplementedError()
1069 def check_options(self):
1070 """Check the the run options are OK.
1072 This should only be called after all options have been processed."""
1074 # Convenience var, so we don't have to keep instantiating this Borg.
1075 ctx = Ctx()
1077 if not self.start_pass <= self.end_pass:
1078 raise InvalidPassError(
1079 'Ending pass must not come before starting pass.')
1081 if not ctx.dry_run and ctx.output_option is None:
1082 raise FatalError('No output option specified.')
1084 if ctx.output_option is not None:
1085 ctx.output_option.check()
1087 if not self.projects:
1088 raise FatalError('No project specified.')
1090 def verify_option_compatibility(self):
1091 """Verify that no options incompatible with --options were used.
1093 The --options option was specified. Verify that no incompatible
1094 options or arguments were specified."""
1096 if self.options.options_incompatible_options or self.args:
1097 if self.options.options_incompatible_options:
1098 oio = self.options.options_incompatible_options
1099 logger.error(
1100 '%s: The following options cannot be used in combination with '
1101 'the --options\n'
1102 'option:\n'
1103 ' %s\n'
1104 % (error_prefix, '\n '.join(oio))
1106 if self.args:
1107 logger.error(
1108 '%s: No cvs-repos-path arguments are allowed with the --options '
1109 'option.\n'
1110 % (error_prefix,)
1112 sys.exit(1)
1114 def process_options_file(self, options_filename):
1115 """Read options from the file named OPTIONS_FILENAME.
1117 Store the run options to SELF."""
1119 g = {
1120 'ctx' : Ctx(),
1121 'run_options' : self,
1123 execfile(options_filename, g)
1125 def usage(self):
1126 self.parser.print_help()