Change checkout_internal to read the revision data during FilterSymbolsPass.
[cvs2svn.git] / cvs2svn_lib / run_options.py
blob751505166a47549f9ece3a8ee1d0f0872e14429b
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 codecs
25 import time
27 from cvs2svn_lib.version import VERSION
28 from cvs2svn_lib import config
29 from cvs2svn_lib.common import warning_prefix
30 from cvs2svn_lib.common import error_prefix
31 from cvs2svn_lib.common import FatalError
32 from cvs2svn_lib.common import CVSTextDecoder
33 from cvs2svn_lib.man_writer import ManWriter
34 from cvs2svn_lib.log import Log
35 from cvs2svn_lib.context import Ctx
36 from cvs2svn_lib.man_writer import ManOption
37 from cvs2svn_lib.pass_manager import InvalidPassError
38 from cvs2svn_lib.revision_manager import NullRevisionRecorder
39 from cvs2svn_lib.revision_manager import NullRevisionExcluder
40 from cvs2svn_lib.rcs_revision_manager import RCSRevisionReader
41 from cvs2svn_lib.cvs_revision_manager import CVSRevisionReader
42 from cvs2svn_lib.checkout_internal import InternalRevisionRecorder
43 from cvs2svn_lib.checkout_internal import InternalRevisionExcluder
44 from cvs2svn_lib.checkout_internal import InternalRevisionReader
45 from cvs2svn_lib.symbol_strategy import AllBranchRule
46 from cvs2svn_lib.symbol_strategy import AllTagRule
47 from cvs2svn_lib.symbol_strategy import BranchIfCommitsRule
48 from cvs2svn_lib.symbol_strategy import ExcludeRegexpStrategyRule
49 from cvs2svn_lib.symbol_strategy import ForceBranchRegexpStrategyRule
50 from cvs2svn_lib.symbol_strategy import ForceTagRegexpStrategyRule
51 from cvs2svn_lib.symbol_strategy import ExcludeTrivialImportBranchRule
52 from cvs2svn_lib.symbol_strategy import HeuristicStrategyRule
53 from cvs2svn_lib.symbol_strategy import UnambiguousUsageRule
54 from cvs2svn_lib.symbol_strategy import HeuristicPreferredParentRule
55 from cvs2svn_lib.symbol_strategy import SymbolHintsFileRule
56 from cvs2svn_lib.symbol_transform import ReplaceSubstringsSymbolTransform
57 from cvs2svn_lib.symbol_transform import RegexpSymbolTransform
58 from cvs2svn_lib.symbol_transform import NormalizePathsSymbolTransform
59 from cvs2svn_lib.property_setters import AutoPropsPropertySetter
60 from cvs2svn_lib.property_setters import CVSBinaryFileDefaultMimeTypeSetter
61 from cvs2svn_lib.property_setters import CVSBinaryFileEOLStyleSetter
62 from cvs2svn_lib.property_setters import CVSRevisionNumberSetter
63 from cvs2svn_lib.property_setters import DefaultEOLStyleSetter
64 from cvs2svn_lib.property_setters import EOLStyleFromMimeTypeSetter
65 from cvs2svn_lib.property_setters import ExecutablePropertySetter
66 from cvs2svn_lib.property_setters import DescriptionPropertySetter
67 from cvs2svn_lib.property_setters import KeywordsPropertySetter
68 from cvs2svn_lib.property_setters import MimeMapper
69 from cvs2svn_lib.property_setters import SVNBinaryFileKeywordsPropertySetter
72 usage = """\
73 Usage: %prog --options OPTIONFILE
74 %prog [OPTION...] OUTPUT-OPTION CVS-REPOS-PATH"""
76 description="""\
77 Convert a CVS repository into a Subversion repository, including history.
78 """
81 class IncompatibleOption(ManOption):
82 """A ManOption that is incompatible with the --options option.
84 Record that the option was used so that error checking can later be
85 done."""
87 def __init__(self, *args, **kw):
88 ManOption.__init__(self, *args, **kw)
90 def take_action(self, action, dest, opt, value, values, parser):
91 oio = parser.values.options_incompatible_options
92 if opt not in oio:
93 oio.append(opt)
94 return ManOption.take_action(
95 self, action, dest, opt, value, values, parser
99 class ContextOption(ManOption):
100 """A ManOption that stores its value to Ctx."""
102 def __init__(self, *args, **kw):
103 if kw.get('action') not in self.STORE_ACTIONS:
104 raise ValueError('Invalid action: %s' % (kw['action'],))
106 self.__compatible_with_option = kw.pop('compatible_with_option', False)
107 self.__action = kw.pop('action')
108 try:
109 self.__dest = kw.pop('dest')
110 except KeyError:
111 opt = args[0]
112 if not opt.startswith('--'):
113 raise ValueError
114 self.__dest = opt[2:].replace('-', '_')
115 if 'const' in kw:
116 self.__const = kw.pop('const')
118 kw['action'] = 'callback'
119 kw['callback'] = self.__callback
121 ManOption.__init__(self, *args, **kw)
123 def __callback(self, option, opt_str, value, parser):
124 if not self.__compatible_with_option:
125 oio = parser.values.options_incompatible_options
126 if opt_str not in oio:
127 oio.append(opt_str)
129 action = self.__action
130 dest = self.__dest
132 if action == "store":
133 setattr(Ctx(), dest, value)
134 elif action == "store_const":
135 setattr(Ctx(), dest, self.__const)
136 elif action == "store_true":
137 setattr(Ctx(), dest, True)
138 elif action == "store_false":
139 setattr(Ctx(), dest, False)
140 elif action == "append":
141 getattr(Ctx(), dest).append(value)
142 elif action == "count":
143 setattr(Ctx(), dest, getattr(Ctx(), dest, 0) + 1)
144 else:
145 raise RuntimeError("unknown action %r" % self.__action)
147 return 1
150 class IncompatibleOptionsException(FatalError):
151 pass
154 # Options that are not allowed to be used with --trunk-only:
155 SYMBOL_OPTIONS = [
156 '--symbol-transform',
157 '--symbol-hints',
158 '--force-branch',
159 '--force-tag',
160 '--exclude',
161 '--keep-trivial-imports',
162 '--symbol-default',
163 '--no-cross-branch-commits',
166 class SymbolOptionsWithTrunkOnlyException(IncompatibleOptionsException):
167 def __init__(self):
168 IncompatibleOptionsException.__init__(
169 self,
170 'The following symbol-related options cannot be used together\n'
171 'with --trunk-only:\n'
172 ' %s'
173 % ('\n '.join(SYMBOL_OPTIONS),)
177 def not_both(opt1val, opt1name, opt2val, opt2name):
178 """Raise an exception if both opt1val and opt2val are set."""
179 if opt1val and opt2val:
180 raise IncompatibleOptionsException(
181 "cannot pass both '%s' and '%s'." % (opt1name, opt2name,)
185 class RunOptions(object):
186 """A place to store meta-options that are used to start the conversion."""
188 # Components of the man page. Attributes set to None here must be set
189 # by subclasses; others may be overridden/augmented by subclasses if
190 # they wish.
191 short_desc = None
192 synopsis = None
193 long_desc = None
194 files = None
195 authors = [
196 u"C. Michael Pilato <cmpilato@collab.net>",
197 u"Greg Stein <gstein@lyra.org>",
198 u"Branko \u010cibej <brane@xbc.nu>",
199 u"Blair Zajac <blair@orcaware.com>",
200 u"Max Bowsher <maxb@ukf.net>",
201 u"Brian Fitzpatrick <fitz@red-bean.com>",
202 u"Tobias Ringstr\u00f6m <tobias@ringstrom.mine.nu>",
203 u"Karl Fogel <kfogel@collab.net>",
204 u"Erik H\u00fclsmann <e.huelsmann@gmx.net>",
205 u"David Summers <david@summersoft.fay.ar.us>",
206 u"Michael Haggerty <mhagger@alum.mit.edu>",
208 see_also = None
210 def __init__(self, progname, cmd_args, pass_manager):
211 """Process the command-line options, storing run options to SELF.
213 PROGNAME is the name of the program, used in the usage string.
214 CMD_ARGS is the list of command-line arguments passed to the
215 program. PASS_MANAGER is an instance of PassManager, needed to
216 help process the -p and --help-passes options."""
218 self.progname = progname
219 self.cmd_args = cmd_args
220 self.pass_manager = pass_manager
221 self.start_pass = 1
222 self.end_pass = self.pass_manager.num_passes
223 self.profiling = False
225 self.projects = []
227 # A list of one list of SymbolStrategyRules for each project:
228 self.project_symbol_strategy_rules = []
230 parser = self.parser = optparse.OptionParser(
231 usage=usage,
232 description=self.get_description(),
233 add_help_option=False,
235 # A place to record any options used that are incompatible with
236 # --options:
237 parser.set_default('options_incompatible_options', [])
239 # Populate the options parser with the options, one group at a
240 # time:
241 parser.add_option_group(self._get_options_file_options_group())
242 parser.add_option_group(self._get_output_options_group())
243 parser.add_option_group(self._get_conversion_options_group())
244 parser.add_option_group(self._get_symbol_handling_options_group())
245 parser.add_option_group(self._get_subversion_properties_options_group())
246 parser.add_option_group(self._get_extraction_options_group())
247 parser.add_option_group(self._get_environment_options_group())
248 parser.add_option_group(self._get_partial_conversion_options_group())
249 parser.add_option_group(self._get_information_options_group())
251 (self.options, self.args) = parser.parse_args(args=self.cmd_args)
253 # Now the log level has been set; log the time when the run started:
254 Log().verbose(
255 time.strftime(
256 'Conversion start time: %Y-%m-%d %I:%M:%S %Z',
257 time.localtime(Log().start_time)
261 if self.options.options_file_found:
262 # Check that no options that are incompatible with --options
263 # were used:
264 self.verify_option_compatibility()
265 else:
266 # --options was not specified. So do the main initialization
267 # based on other command-line options:
268 self.process_options()
270 # Check for problems with the options:
271 self.check_options()
273 def get_description(self):
274 return description
276 def _get_options_file_options_group(self):
277 group = OptionGroup(
278 self.parser, 'Configuration via options file'
280 self.parser.set_default('options_file_found', False)
281 group.add_option(ManOption(
282 '--options', type='string',
283 action='callback', callback=self.callback_options,
284 help=(
285 'read the conversion options from PATH. This '
286 'method allows more flexibility than using '
287 'command-line options. See documentation for info'
289 man_help=(
290 'Read the conversion options from \\fIpath\\fR instead of from '
291 'the command line. This option allows far more conversion '
292 'flexibility than can be achieved using the command-line alone. '
293 'See the documentation for more information. Only the following '
294 'command-line options are allowed in combination with '
295 '\\fB--options\\fR: \\fB-h\\fR/\\fB--help\\fR, '
296 '\\fB--help-passes\\fR, \\fB--version\\fR, '
297 '\\fB-v\\fR/\\fB--verbose\\fR, \\fB-q\\fR/\\fB--quiet\\fR, '
298 '\\fB-p\\fR/\\fB--pass\\fR/\\fB--passes\\fR, \\fB--dry-run\\fR, '
299 '\\fB--profile\\fR, \\fB--trunk-only\\fR, \\fB--encoding\\fR, '
300 'and \\fB--fallback-encoding\\fR. '
301 'Options are processed in the order specified on the command '
302 'line.'
304 metavar='PATH',
306 return group
308 def _get_output_options_group(self):
309 group = OptionGroup(self.parser, 'Output options')
310 return group
312 def _get_conversion_options_group(self):
313 group = OptionGroup(self.parser, 'Conversion options')
314 group.add_option(ContextOption(
315 '--trunk-only',
316 action='store_true',
317 compatible_with_option=True,
318 help='convert only trunk commits, not tags nor branches',
319 man_help=(
320 'Convert only trunk commits, not tags nor branches.'
323 group.add_option(ManOption(
324 '--encoding', type='string',
325 action='callback', callback=self.callback_encoding,
326 help=(
327 'encoding for paths and log messages in CVS repos. '
328 'If option is specified multiple times, encoders '
329 'are tried in order until one succeeds. See '
330 'http://docs.python.org/lib/standard-encodings.html '
331 'for a list of standard Python encodings.'
333 man_help=(
334 'Use \\fIencoding\\fR as the encoding for filenames, log '
335 'messages, and author names in the CVS repos. This option '
336 'may be specified multiple times, in which case the encodings '
337 'are tried in order until one succeeds. Default: ascii. See '
338 'http://docs.python.org/lib/standard-encodings.html for a list '
339 'of other standard encodings.'
341 metavar='ENC',
343 group.add_option(ManOption(
344 '--fallback-encoding', type='string',
345 action='callback', callback=self.callback_fallback_encoding,
346 help='If all --encodings fail, use lossy encoding with ENC',
347 man_help=(
348 'If none of the encodings specified with \\fB--encoding\\fR '
349 'succeed in decoding an author name or log message, then fall '
350 'back to using \\fIencoding\\fR in lossy \'replace\' mode. '
351 'Use of this option may cause information to be lost, but at '
352 'least it allows the conversion to run to completion. This '
353 'option only affects the encoding of log messages and author '
354 'names; there is no fallback encoding for filenames. (By '
355 'using an \\fB--options\\fR file, it is possible to specify '
356 'a fallback encoding for filenames.) Default: disabled.'
358 metavar='ENC',
360 group.add_option(ContextOption(
361 '--retain-conflicting-attic-files',
362 action='store_true',
363 help=(
364 'if a file appears both in and out of '
365 'the CVS Attic, then leave the attic version in a '
366 'SVN directory called "Attic"'
368 man_help=(
369 'If a file appears both inside an outside of the CVS attic, '
370 'retain the attic version in an SVN subdirectory called '
371 '\'Attic\'. (Normally this situation is treated as a fatal '
372 'error.)'
376 return group
378 def _get_symbol_handling_options_group(self):
379 group = OptionGroup(self.parser, 'Symbol handling')
380 self.parser.set_default('symbol_transforms', [])
381 group.add_option(IncompatibleOption(
382 '--symbol-transform', type='string',
383 action='callback', callback=self.callback_symbol_transform,
384 help=(
385 'transform symbol names from P to S, where P and S '
386 'use Python regexp and reference syntax '
387 'respectively. P must match the whole symbol name'
389 man_help=(
390 'Transform RCS/CVS symbol names before entering them into '
391 'Subversion. \\fIpattern\\fR is a Python regexp pattern that '
392 'is matches against the entire symbol name; \\fIreplacement\\fR '
393 'is a replacement using Python\'s regexp reference syntax. '
394 'You may specify any number of these options; they will be '
395 'applied in the order given on the command line.'
397 metavar='P:S',
399 self.parser.set_default('symbol_strategy_rules', [])
400 group.add_option(IncompatibleOption(
401 '--symbol-hints', type='string',
402 action='callback', callback=self.callback_symbol_hints,
403 help='read symbol conversion hints from PATH',
404 man_help=(
405 'Read symbol conversion hints from \\fIpath\\fR. The format of '
406 '\\fIpath\\fR is the same as the format output by '
407 '\\fB--write-symbol-info\\fR, namely a text file with four '
408 'whitespace-separated columns: \\fIproject-id\\fR, '
409 '\\fIsymbol\\fR, \\fIconversion\\fR, and '
410 '\\fIparent-lod-name\\fR. \\fIproject-id\\fR is the numerical '
411 'ID of the project to which the symbol belongs, counting from '
412 '0. \\fIproject-id\\fR can be set to \'.\' if '
413 'project-specificity is not needed. \\fIsymbol-name\\fR is the '
414 'name of the symbol being specified. \\fIconversion\\fR '
415 'specifies how the symbol should be converted, and can be one '
416 'of the values \'branch\', \'tag\', or \'exclude\'. If '
417 '\\fIconversion\\fR is \'.\', then this rule does not affect '
418 'how the symbol is converted. \\fIparent-lod-name\\fR is the '
419 'name of the symbol from which this symbol should sprout, or '
420 '\'.trunk.\' if the symbol should sprout from trunk. If '
421 '\\fIparent-lod-name\\fR is omitted or \'.\', then this rule '
422 'does not affect the preferred parent of this symbol. The file '
423 'may contain blank lines or comment lines (lines whose first '
424 'non-whitespace character is \'#\').'
426 metavar='PATH',
428 self.parser.set_default('symbol_default', 'heuristic')
429 group.add_option(IncompatibleOption(
430 '--symbol-default', type='choice',
431 choices=['heuristic', 'strict', 'branch', 'tag'],
432 action='store',
433 help=(
434 'specify how ambiguous symbols are converted. '
435 'OPT is "heuristic" (default), "strict", "branch", '
436 'or "tag"'
438 man_help=(
439 'Specify how to convert ambiguous symbols (those that appear in '
440 'the CVS archive as both branches and tags). \\fIopt\\fR must '
441 'be \'heuristic\' (decide how to treat each ambiguous symbol '
442 'based on whether it was used more often as a branch/tag in '
443 'CVS), \'strict\' (no default; every ambiguous symbol has to be '
444 'resolved manually using \\fB--force-branch\\fR, '
445 '\\fB--force-tag\\fR, or \\fB--exclude\\fR), \'branch\' (treat '
446 'every ambiguous symbol as a branch), or \'tag\' (treat every '
447 'ambiguous symbol as a tag). The default is \'heuristic\'.'
449 metavar='OPT',
451 group.add_option(IncompatibleOption(
452 '--force-branch', type='string',
453 action='callback', callback=self.callback_force_branch,
454 help='force symbols matching REGEXP to be branches',
455 man_help=(
456 'Force symbols whose names match \\fIregexp\\fR to be branches. '
457 '\\fIregexp\\fR must match the whole symbol name.'
459 metavar='REGEXP',
461 group.add_option(IncompatibleOption(
462 '--force-tag', type='string',
463 action='callback', callback=self.callback_force_tag,
464 help='force symbols matching REGEXP to be tags',
465 man_help=(
466 'Force symbols whose names match \\fIregexp\\fR to be tags. '
467 '\\fIregexp\\fR must match the whole symbol name.'
469 metavar='REGEXP',
471 group.add_option(IncompatibleOption(
472 '--exclude', type='string',
473 action='callback', callback=self.callback_exclude,
474 help='exclude branches and tags matching REGEXP',
475 man_help=(
476 'Exclude branches and tags whose names match \\fIregexp\\fR '
477 'from the conversion. \\fIregexp\\fR must match the whole '
478 'symbol name.'
480 metavar='REGEXP',
482 self.parser.set_default('keep_trivial_imports', False)
483 group.add_option(IncompatibleOption(
484 '--keep-trivial-imports',
485 action='store_true',
486 help=(
487 'do not exclude branches that were only used for '
488 'a single import (usually these are unneeded)'
490 man_help=(
491 'Do not exclude branches that were only used for a single '
492 'import. (By default such branches are excluded because they '
493 'are usually created by the inappropriate use of \\fBcvs '
494 'import\\fR.)'
498 return group
500 def _get_subversion_properties_options_group(self):
501 group = OptionGroup(self.parser, 'Subversion properties')
502 group.add_option(ContextOption(
503 '--username', type='string',
504 action='store',
505 help='username for cvs2svn-synthesized commits',
506 man_help=(
507 'Set the default username to \\fIname\\fR when cvs2svn needs '
508 'to generate a commit for which CVS does not record the '
509 'original username. This happens when a branch or tag is '
510 'created. The default is to use no author at all for such '
511 'commits.'
513 metavar='NAME',
515 self.parser.set_default('auto_props_files', [])
516 group.add_option(IncompatibleOption(
517 '--auto-props', type='string',
518 action='append', dest='auto_props_files',
519 help=(
520 'set file properties from the auto-props section '
521 'of a file in svn config format'
523 man_help=(
524 'Specify a file in the format of Subversion\'s config file, '
525 'whose [auto-props] section can be used to set arbitrary '
526 'properties on files in the Subversion repository based on '
527 'their filenames. (The [auto-props] section header must be '
528 'present; other sections of the config file, including the '
529 'enable-auto-props setting, are ignored.) Filenames are matched '
530 'to the filename patterns case-insensitively.'
533 metavar='FILE',
535 self.parser.set_default('mime_types_files', [])
536 group.add_option(IncompatibleOption(
537 '--mime-types', type='string',
538 action='append', dest='mime_types_files',
539 help=(
540 'specify an apache-style mime.types file for setting '
541 'svn:mime-type'
543 man_help=(
544 'Specify an apache-style mime.types \\fIfile\\fR for setting '
545 'svn:mime-type.'
547 metavar='FILE',
549 self.parser.set_default('eol_from_mime_type', False)
550 group.add_option(IncompatibleOption(
551 '--eol-from-mime-type',
552 action='store_true',
553 help='set svn:eol-style from mime type if known',
554 man_help=(
555 'For files that don\'t have the kb expansion mode but have a '
556 'known mime type, set the eol-style based on the mime type. '
557 'For such files, set svn:eol-style to "native" if the mime type '
558 'begins with "text/", and leave it unset (i.e., no EOL '
559 'translation) otherwise. Files with unknown mime types are '
560 'not affected by this option. This option has no effect '
561 'unless the \\fB--mime-types\\fR option is also specified.'
564 group.add_option(IncompatibleOption(
565 '--default-eol', type='choice',
566 choices=['binary', 'native', 'CRLF', 'LF', 'CR'],
567 action='store',
568 help=(
569 'default svn:eol-style for non-binary files with '
570 'undetermined mime types. STYLE is "binary" '
571 '(default), "native", "CRLF", "LF", or "CR"'
573 man_help=(
574 'Set svn:eol-style to \\fIstyle\\fR for files that don\'t have '
575 'the CVS \'kb\' expansion mode and whose end-of-line '
576 'translation mode hasn\'t been determined by one of the other '
577 'options. \\fIstyle\\fR must be \'binary\' (default), '
578 '\'native\', \'CRLF\', \'LF\', or \'CR\'.'
580 metavar='STYLE',
582 self.parser.set_default('keywords_off', False)
583 group.add_option(IncompatibleOption(
584 '--keywords-off',
585 action='store_true',
586 help=(
587 'don\'t set svn:keywords on any files (by default, '
588 'cvs2svn sets svn:keywords on non-binary files to "%s")'
589 % (config.SVN_KEYWORDS_VALUE,)
591 man_help=(
592 'By default, cvs2svn sets svn:keywords on CVS files to "author '
593 'id date" if the mode of the RCS file in question is either kv, '
594 'kvl or unset. If you use the --keywords-off switch, cvs2svn '
595 'will not set svn:keywords for any file. While this will not '
596 'touch the keywords in the contents of your files, Subversion '
597 'will not expand them.'
600 group.add_option(ContextOption(
601 '--keep-cvsignore',
602 action='store_true',
603 help=(
604 'keep .cvsignore files (in addition to creating '
605 'the analogous svn:ignore properties)'
607 man_help=(
608 'Include \\fI.cvsignore\\fR files in the output. (Normally '
609 'they are unneeded because cvs2svn sets the corresponding '
610 '\\fIsvn:ignore\\fR properties.)'
613 group.add_option(IncompatibleOption(
614 '--cvs-revnums',
615 action='callback', callback=self.callback_cvs_revnums,
616 help='record CVS revision numbers as file properties',
617 man_help=(
618 'Record CVS revision numbers as file properties in the '
619 'Subversion repository. (Note that unless it is removed '
620 'explicitly, the last CVS revision number will remain '
621 'associated with the file even after the file is changed '
622 'within Subversion.)'
626 # Deprecated options:
627 group.add_option(IncompatibleOption(
628 '--no-default-eol',
629 action='store_const', dest='default_eol', const=None,
630 help=optparse.SUPPRESS_HELP,
631 man_help=optparse.SUPPRESS_HELP,
633 self.parser.set_default('auto_props_ignore_case', True)
634 # True is the default now, so this option has no effect:
635 group.add_option(IncompatibleOption(
636 '--auto-props-ignore-case',
637 action='store_true',
638 help=optparse.SUPPRESS_HELP,
639 man_help=optparse.SUPPRESS_HELP,
642 return group
644 def _get_extraction_options_group(self):
645 group = OptionGroup(self.parser, 'Extraction options')
647 return group
649 def _add_use_internal_co_option(self, group):
650 self.parser.set_default('use_internal_co', False)
651 group.add_option(IncompatibleOption(
652 '--use-internal-co',
653 action='store_true',
654 help=(
655 'use internal code to extract revision contents '
656 '(fastest but disk space intensive) (default)'
658 man_help=(
659 'Use internal code to extract revision contents. This '
660 'is up to 50% faster than using \\fB--use-rcs\\fR, but needs '
661 'a lot of disk space: roughly the size of your CVS repository '
662 'plus the peak size of a complete checkout of the repository '
663 'with all branches that existed and still had commits pending '
664 'at a given time. This option is the default.'
668 def _add_use_cvs_option(self, group):
669 self.parser.set_default('use_cvs', False)
670 group.add_option(IncompatibleOption(
671 '--use-cvs',
672 action='store_true',
673 help=(
674 'use CVS to extract revision contents (slower than '
675 '--use-internal-co or --use-rcs)'
677 man_help=(
678 'Use CVS to extract revision contents. This option is slower '
679 'than \\fB--use-internal-co\\fR or \\fB--use-rcs\\fR.'
683 def _add_use_rcs_option(self, group):
684 self.parser.set_default('use_rcs', False)
685 group.add_option(IncompatibleOption(
686 '--use-rcs',
687 action='store_true',
688 help=(
689 'use RCS to extract revision contents (faster than '
690 '--use-cvs but fails in some cases)'
692 man_help=(
693 'Use RCS \'co\' to extract revision contents. This option is '
694 'faster than \\fB--use-cvs\\fR but fails in some cases.'
698 def _get_environment_options_group(self):
699 group = OptionGroup(self.parser, 'Environment options')
700 group.add_option(ContextOption(
701 '--tmpdir', type='string',
702 action='store',
703 help=(
704 'directory to use for temporary data files '
705 '(default "cvs2svn-tmp")'
707 man_help=(
708 'Set the \\fIpath\\fR to use for temporary data. Default '
709 'is a directory called \\fIcvs2svn-tmp\\fR under the current '
710 'directory.'
712 metavar='PATH',
714 self.parser.set_default('co_executable', config.CO_EXECUTABLE)
715 group.add_option(IncompatibleOption(
716 '--co', type='string',
717 action='store', dest='co_executable',
718 help='path to the "co" program (required if --use-rcs)',
719 man_help=(
720 'Path to the \\fIco\\fR program. (\\fIco\\fR is needed if the '
721 '\\fB--use-rcs\\fR option is used.)'
723 metavar='PATH',
725 self.parser.set_default('cvs_executable', config.CVS_EXECUTABLE)
726 group.add_option(IncompatibleOption(
727 '--cvs', type='string',
728 action='store', dest='cvs_executable',
729 help='path to the "cvs" program (required if --use-cvs)',
730 man_help=(
731 'Path to the \\fIcvs\\fR program. (\\fIcvs\\fR is needed if the '
732 '\\fB--use-cvs\\fR option is used.)'
734 metavar='PATH',
737 return group
739 def _get_partial_conversion_options_group(self):
740 group = OptionGroup(self.parser, 'Partial conversions')
741 group.add_option(ManOption(
742 '--pass', type='string',
743 action='callback', callback=self.callback_passes,
744 help='execute only specified PASS of conversion',
745 man_help=(
746 'Execute only pass \\fIpass\\fR of the conversion. '
747 '\\fIpass\\fR can be specified by name or by number (see '
748 '\\fB--help-passes\\fR).'
750 metavar='PASS',
752 group.add_option(ManOption(
753 '--passes', '-p', type='string',
754 action='callback', callback=self.callback_passes,
755 help=(
756 'execute passes START through END, inclusive (PASS, '
757 'START, and END can be pass names or numbers)'
759 man_help=(
760 'Execute passes \\fIstart\\fR through \\fIend\\fR of the '
761 'conversion (inclusive). \\fIstart\\fR and \\fIend\\fR can be '
762 'specified by name or by number (see \\fB--help-passes\\fR). '
763 'If \\fIstart\\fR or \\fIend\\fR is missing, it defaults to '
764 'the first or last pass, respectively. For this to work the '
765 'earlier passes must have been completed before on the '
766 'same CVS repository, and the generated data files must be '
767 'in the temporary directory (see \\fB--tmpdir\\fR).'
769 metavar='[START]:[END]',
772 return group
774 def _get_information_options_group(self):
775 group = OptionGroup(self.parser, 'Information options')
776 group.add_option(ManOption(
777 '--version',
778 action='callback', callback=self.callback_version,
779 help='print the version number',
780 man_help='Print the version number.',
782 group.add_option(ManOption(
783 '--help', '-h',
784 action="help",
785 help='print this usage message and exit with success',
786 man_help='Print the usage message and exit with success.',
788 group.add_option(ManOption(
789 '--help-passes',
790 action='callback', callback=self.callback_help_passes,
791 help='list the available passes and their numbers',
792 man_help=(
793 'Print the numbers and names of the conversion passes and '
794 'exit with success.'
797 group.add_option(ManOption(
798 '--man',
799 action='callback', callback=self.callback_manpage,
800 help='write the manpage for this program to standard output',
801 man_help=(
802 'Output the unix-style manpage for this program to standard '
803 'output.'
806 group.add_option(ManOption(
807 '--verbose', '-v',
808 action='callback', callback=self.callback_verbose,
809 help='verbose (may be specified twice for debug output)',
810 man_help=(
811 'Print more information while running. This option may be '
812 'specified twice to output voluminous debugging information.'
815 group.add_option(ManOption(
816 '--quiet', '-q',
817 action='callback', callback=self.callback_quiet,
818 help='quiet (may be specified twice for very quiet)',
819 man_help=(
820 'Print less information while running. This option may be '
821 'specified twice to suppress all non-error output.'
824 group.add_option(ContextOption(
825 '--write-symbol-info', type='string',
826 action='store', dest='symbol_info_filename',
827 help='write information and statistics about CVS symbols to PATH.',
828 man_help=(
829 'Write to \\fIpath\\fR symbol statistics and information about '
830 'how symbols were converted during CollateSymbolsPass.'
832 metavar='PATH',
834 group.add_option(ContextOption(
835 '--skip-cleanup',
836 action='store_true',
837 help='prevent the deletion of intermediate files',
838 man_help='Prevent the deletion of temporary files.',
840 group.add_option(ManOption(
841 '--profile',
842 action='callback', callback=self.callback_profile,
843 help='profile with \'hotshot\' (into file cvs2svn.hotshot)',
844 man_help=(
845 'Profile with \'hotshot\' (into file \\fIcvs2svn.hotshot\\fR).'
849 return group
851 def callback_options(self, option, opt_str, value, parser):
852 parser.values.options_file_found = True
853 self.process_options_file(value)
855 def callback_encoding(self, option, opt_str, value, parser):
856 ctx = Ctx()
858 try:
859 ctx.cvs_author_decoder.add_encoding(value)
860 ctx.cvs_log_decoder.add_encoding(value)
861 ctx.cvs_filename_decoder.add_encoding(value)
862 except LookupError, e:
863 raise FatalError(str(e))
865 def callback_fallback_encoding(self, option, opt_str, value, parser):
866 ctx = Ctx()
868 try:
869 ctx.cvs_author_decoder.set_fallback_encoding(value)
870 ctx.cvs_log_decoder.set_fallback_encoding(value)
871 # Don't use fallback_encoding for filenames.
872 except LookupError, e:
873 raise FatalError(str(e))
875 def callback_help_passes(self, option, opt_str, value, parser):
876 self.pass_manager.help_passes()
877 sys.exit(0)
879 def callback_manpage(self, option, opt_str, value, parser):
880 f = codecs.getwriter('utf_8')(sys.stdout)
881 writer = ManWriter(parser,
882 section='1',
883 date=datetime.date.today(),
884 source='Version %s' % (VERSION,),
885 manual='User Commands',
886 short_desc=self.short_desc,
887 synopsis=self.synopsis,
888 long_desc=self.long_desc,
889 files=self.files,
890 authors=self.authors,
891 see_also=self.see_also)
892 writer.write_manpage(f)
893 sys.exit(0)
895 def callback_version(self, option, opt_str, value, parser):
896 sys.stdout.write(
897 '%s version %s\n' % (self.progname, VERSION)
899 sys.exit(0)
901 def callback_verbose(self, option, opt_str, value, parser):
902 Log().increase_verbosity()
904 def callback_quiet(self, option, opt_str, value, parser):
905 Log().decrease_verbosity()
907 def callback_passes(self, option, opt_str, value, parser):
908 if value.find(':') >= 0:
909 start_pass, end_pass = value.split(':')
910 self.start_pass = self.pass_manager.get_pass_number(start_pass, 1)
911 self.end_pass = self.pass_manager.get_pass_number(
912 end_pass, self.pass_manager.num_passes
914 else:
915 self.end_pass = \
916 self.start_pass = \
917 self.pass_manager.get_pass_number(value)
919 def callback_profile(self, option, opt_str, value, parser):
920 self.profiling = True
922 def callback_symbol_hints(self, option, opt_str, value, parser):
923 parser.values.symbol_strategy_rules.append(SymbolHintsFileRule(value))
925 def callback_force_branch(self, option, opt_str, value, parser):
926 parser.values.symbol_strategy_rules.append(
927 ForceBranchRegexpStrategyRule(value)
930 def callback_force_tag(self, option, opt_str, value, parser):
931 parser.values.symbol_strategy_rules.append(
932 ForceTagRegexpStrategyRule(value)
935 def callback_exclude(self, option, opt_str, value, parser):
936 parser.values.symbol_strategy_rules.append(
937 ExcludeRegexpStrategyRule(value)
940 def callback_cvs_revnums(self, option, opt_str, value, parser):
941 Ctx().svn_property_setters.append(CVSRevisionNumberSetter())
943 def callback_symbol_transform(self, option, opt_str, value, parser):
944 [pattern, replacement] = value.split(":")
945 try:
946 parser.values.symbol_transforms.append(
947 RegexpSymbolTransform(pattern, replacement)
949 except re.error:
950 raise FatalError("'%s' is not a valid regexp." % (pattern,))
952 # Common to SVNRunOptions, HgRunOptions (GitRunOptions and
953 # BzrRunOptions do not support --use-internal-co, so cannot use this).
954 def process_all_extraction_options(self):
955 ctx = Ctx()
956 options = self.options
958 not_both(options.use_rcs, '--use-rcs',
959 options.use_cvs, '--use-cvs')
961 not_both(options.use_rcs, '--use-rcs',
962 options.use_internal_co, '--use-internal-co')
964 not_both(options.use_cvs, '--use-cvs',
965 options.use_internal_co, '--use-internal-co')
967 if options.use_rcs:
968 ctx.revision_recorder = NullRevisionRecorder()
969 ctx.revision_excluder = NullRevisionExcluder()
970 ctx.revision_reader = RCSRevisionReader(options.co_executable)
971 elif options.use_cvs:
972 ctx.revision_recorder = NullRevisionRecorder()
973 ctx.revision_excluder = NullRevisionExcluder()
974 ctx.revision_reader = CVSRevisionReader(options.cvs_executable)
975 else:
976 # --use-internal-co is the default:
977 ctx.revision_recorder = InternalRevisionRecorder()
978 ctx.revision_excluder = InternalRevisionExcluder(compress=True)
979 ctx.revision_reader = InternalRevisionReader(compress=True)
981 def process_symbol_strategy_options(self):
982 """Process symbol strategy-related options."""
984 ctx = Ctx()
985 options = self.options
987 # Add the standard symbol name cleanup rules:
988 self.options.symbol_transforms.extend([
989 ReplaceSubstringsSymbolTransform('\\','/'),
990 # Remove leading, trailing, and repeated slashes:
991 NormalizePathsSymbolTransform(),
994 if ctx.trunk_only:
995 if options.symbol_strategy_rules or options.keep_trivial_imports:
996 raise SymbolOptionsWithTrunkOnlyException()
998 else:
999 if not options.keep_trivial_imports:
1000 options.symbol_strategy_rules.append(ExcludeTrivialImportBranchRule())
1002 options.symbol_strategy_rules.append(UnambiguousUsageRule())
1003 if options.symbol_default == 'strict':
1004 pass
1005 elif options.symbol_default == 'branch':
1006 options.symbol_strategy_rules.append(AllBranchRule())
1007 elif options.symbol_default == 'tag':
1008 options.symbol_strategy_rules.append(AllTagRule())
1009 elif options.symbol_default == 'heuristic':
1010 options.symbol_strategy_rules.append(BranchIfCommitsRule())
1011 options.symbol_strategy_rules.append(HeuristicStrategyRule())
1012 else:
1013 assert False
1015 # Now add a rule whose job it is to pick the preferred parents of
1016 # branches and tags:
1017 options.symbol_strategy_rules.append(HeuristicPreferredParentRule())
1019 def process_property_setter_options(self):
1020 """Process the options that set SVN properties."""
1022 ctx = Ctx()
1023 options = self.options
1025 for value in options.auto_props_files:
1026 ctx.svn_property_setters.append(
1027 AutoPropsPropertySetter(value, options.auto_props_ignore_case)
1030 for value in options.mime_types_files:
1031 ctx.svn_property_setters.append(MimeMapper(value))
1033 ctx.svn_property_setters.append(CVSBinaryFileEOLStyleSetter())
1035 ctx.svn_property_setters.append(CVSBinaryFileDefaultMimeTypeSetter())
1037 if options.eol_from_mime_type:
1038 ctx.svn_property_setters.append(EOLStyleFromMimeTypeSetter())
1040 ctx.svn_property_setters.append(
1041 DefaultEOLStyleSetter(options.default_eol)
1044 ctx.svn_property_setters.append(SVNBinaryFileKeywordsPropertySetter())
1046 if not options.keywords_off:
1047 ctx.svn_property_setters.append(
1048 KeywordsPropertySetter(config.SVN_KEYWORDS_VALUE))
1050 ctx.svn_property_setters.append(ExecutablePropertySetter())
1052 ctx.svn_property_setters.append(DescriptionPropertySetter())
1054 def process_options(self):
1055 """Do the main configuration based on command-line options.
1057 This method is only called if the --options option was not
1058 specified."""
1060 raise NotImplementedError()
1062 def check_options(self):
1063 """Check the the run options are OK.
1065 This should only be called after all options have been processed."""
1067 # Convenience var, so we don't have to keep instantiating this Borg.
1068 ctx = Ctx()
1070 if not self.start_pass <= self.end_pass:
1071 raise InvalidPassError(
1072 'Ending pass must not come before starting pass.')
1074 if not ctx.dry_run and ctx.output_option is None:
1075 raise FatalError('No output option specified.')
1077 if ctx.output_option is not None:
1078 ctx.output_option.check()
1080 if not self.projects:
1081 raise FatalError('No project specified.')
1083 def verify_option_compatibility(self):
1084 """Verify that no options incompatible with --options were used.
1086 The --options option was specified. Verify that no incompatible
1087 options or arguments were specified."""
1089 if self.options.options_incompatible_options or self.args:
1090 if self.options.options_incompatible_options:
1091 oio = self.options.options_incompatible_options
1092 Log().error(
1093 '%s: The following options cannot be used in combination with '
1094 'the --options\n'
1095 'option:\n'
1096 ' %s\n'
1097 % (error_prefix, '\n '.join(oio))
1099 if self.args:
1100 Log().error(
1101 '%s: No cvs-repos-path arguments are allowed with the --options '
1102 'option.\n'
1103 % (error_prefix,)
1105 sys.exit(1)
1107 def process_options_file(self, options_filename):
1108 """Read options from the file named OPTIONS_FILENAME.
1110 Store the run options to SELF."""
1112 g = {
1113 'ctx' : Ctx(),
1114 'run_options' : self,
1116 execfile(options_filename, g)
1118 def usage(self):
1119 self.parser.print_help()