run_options: Change 'authors' from a string to a list of author names.
[cvs2svn.git] / cvs2svn_lib / run_options.py
blobe561f5d66ab51ffc0708adcc8c2280b28f44e02a
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.symbol_strategy import AllBranchRule
39 from cvs2svn_lib.symbol_strategy import AllTagRule
40 from cvs2svn_lib.symbol_strategy import BranchIfCommitsRule
41 from cvs2svn_lib.symbol_strategy import ExcludeRegexpStrategyRule
42 from cvs2svn_lib.symbol_strategy import ForceBranchRegexpStrategyRule
43 from cvs2svn_lib.symbol_strategy import ForceTagRegexpStrategyRule
44 from cvs2svn_lib.symbol_strategy import ExcludeTrivialImportBranchRule
45 from cvs2svn_lib.symbol_strategy import HeuristicStrategyRule
46 from cvs2svn_lib.symbol_strategy import UnambiguousUsageRule
47 from cvs2svn_lib.symbol_strategy import HeuristicPreferredParentRule
48 from cvs2svn_lib.symbol_strategy import SymbolHintsFileRule
49 from cvs2svn_lib.symbol_transform import ReplaceSubstringsSymbolTransform
50 from cvs2svn_lib.symbol_transform import RegexpSymbolTransform
51 from cvs2svn_lib.symbol_transform import NormalizePathsSymbolTransform
52 from cvs2svn_lib.property_setters import AutoPropsPropertySetter
53 from cvs2svn_lib.property_setters import CVSBinaryFileDefaultMimeTypeSetter
54 from cvs2svn_lib.property_setters import CVSBinaryFileEOLStyleSetter
55 from cvs2svn_lib.property_setters import CVSRevisionNumberSetter
56 from cvs2svn_lib.property_setters import DefaultEOLStyleSetter
57 from cvs2svn_lib.property_setters import EOLStyleFromMimeTypeSetter
58 from cvs2svn_lib.property_setters import ExecutablePropertySetter
59 from cvs2svn_lib.property_setters import KeywordsPropertySetter
60 from cvs2svn_lib.property_setters import MimeMapper
61 from cvs2svn_lib.property_setters import SVNBinaryFileKeywordsPropertySetter
64 usage = """\
65 Usage: %prog --options OPTIONFILE
66 %prog [OPTION...] OUTPUT-OPTION CVS-REPOS-PATH"""
68 description="""\
69 Convert a CVS repository into a Subversion repository, including history.
70 """
73 class IncompatibleOption(ManOption):
74 """A ManOption that is incompatible with the --options option.
76 Record that the option was used so that error checking can later be
77 done."""
79 def __init__(self, *args, **kw):
80 ManOption.__init__(self, *args, **kw)
82 def take_action(self, action, dest, opt, value, values, parser):
83 oio = parser.values.options_incompatible_options
84 if opt not in oio:
85 oio.append(opt)
86 return ManOption.take_action(
87 self, action, dest, opt, value, values, parser
91 class ContextOption(ManOption):
92 """A ManOption that stores its value to Ctx."""
94 def __init__(self, *args, **kw):
95 if kw.get('action') not in self.STORE_ACTIONS:
96 raise ValueError('Invalid action: %s' % (kw['action'],))
98 self.__compatible_with_option = kw.pop('compatible_with_option', False)
99 self.__action = kw.pop('action')
100 try:
101 self.__dest = kw.pop('dest')
102 except KeyError:
103 opt = args[0]
104 if not opt.startswith('--'):
105 raise ValueError
106 self.__dest = opt[2:].replace('-', '_')
107 if 'const' in kw:
108 self.__const = kw.pop('const')
110 kw['action'] = 'callback'
111 kw['callback'] = self.__callback
113 ManOption.__init__(self, *args, **kw)
115 def __callback(self, option, opt_str, value, parser):
116 if not self.__compatible_with_option:
117 oio = parser.values.options_incompatible_options
118 if opt_str not in oio:
119 oio.append(opt_str)
121 action = self.__action
122 dest = self.__dest
124 if action == "store":
125 setattr(Ctx(), dest, value)
126 elif action == "store_const":
127 setattr(Ctx(), dest, self.__const)
128 elif action == "store_true":
129 setattr(Ctx(), dest, True)
130 elif action == "store_false":
131 setattr(Ctx(), dest, False)
132 elif action == "append":
133 getattr(Ctx(), dest).append(value)
134 elif action == "count":
135 setattr(Ctx(), dest, getattr(Ctx(), dest, 0) + 1)
136 else:
137 raise RuntimeError("unknown action %r" % self.__action)
139 return 1
142 class IncompatibleOptionsException(FatalError):
143 pass
146 # Options that are not allowed to be used with --trunk-only:
147 SYMBOL_OPTIONS = [
148 '--symbol-transform',
149 '--symbol-hints',
150 '--force-branch',
151 '--force-tag',
152 '--exclude',
153 '--keep-trivial-imports',
154 '--symbol-default',
155 '--no-cross-branch-commits',
158 class SymbolOptionsWithTrunkOnlyException(IncompatibleOptionsException):
159 def __init__(self):
160 IncompatibleOptionsException.__init__(
161 self,
162 'The following symbol-related options cannot be used together\n'
163 'with --trunk-only:\n'
164 ' %s'
165 % ('\n '.join(SYMBOL_OPTIONS),)
169 def not_both(opt1val, opt1name, opt2val, opt2name):
170 """Raise an exception if both opt1val and opt2val are set."""
171 if opt1val and opt2val:
172 raise IncompatibleOptionsException(
173 "cannot pass both '%s' and '%s'." % (opt1name, opt2name,)
177 class RunOptions(object):
178 """A place to store meta-options that are used to start the conversion."""
180 # Components of the man page. Attributes set to None here must be set
181 # by subclasses; others may be overridden/augmented by subclasses if
182 # they wish.
183 short_desc = None
184 synopsis = None
185 long_desc = None
186 files = None
187 authors = [
188 u"C. Michael Pilato <cmpilato@collab.net>",
189 u"Greg Stein <gstein@lyra.org>",
190 u"Branko \u010cibej <brane@xbc.nu>",
191 u"Blair Zajac <blair@orcaware.com>",
192 u"Max Bowsher <maxb@ukf.net>",
193 u"Brian Fitzpatrick <fitz@red-bean.com>",
194 u"Tobias Ringstr\u00f6m <tobias@ringstrom.mine.nu>",
195 u"Karl Fogel <kfogel@collab.net>",
196 u"Erik H\u00fclsmann <e.huelsmann@gmx.net>",
197 u"David Summers <david@summersoft.fay.ar.us>",
198 u"Michael Haggerty <mhagger@alum.mit.edu>",
200 see_also = None
202 def __init__(self, progname, cmd_args, pass_manager):
203 """Process the command-line options, storing run options to SELF.
205 PROGNAME is the name of the program, used in the usage string.
206 CMD_ARGS is the list of command-line arguments passed to the
207 program. PASS_MANAGER is an instance of PassManager, needed to
208 help process the -p and --help-passes options."""
210 self.progname = progname
211 self.cmd_args = cmd_args
212 self.pass_manager = pass_manager
213 self.start_pass = 1
214 self.end_pass = self.pass_manager.num_passes
215 self.profiling = False
217 self.projects = []
219 # A list of one list of SymbolStrategyRules for each project:
220 self.project_symbol_strategy_rules = []
222 parser = self.parser = optparse.OptionParser(
223 usage=usage,
224 description=self.get_description(),
225 add_help_option=False,
227 # A place to record any options used that are incompatible with
228 # --options:
229 parser.set_default('options_incompatible_options', [])
231 # Populate the options parser with the options, one group at a
232 # time:
233 parser.add_option_group(self._get_options_file_options_group())
234 parser.add_option_group(self._get_output_options_group())
235 parser.add_option_group(self._get_conversion_options_group())
236 parser.add_option_group(self._get_symbol_handling_options_group())
237 parser.add_option_group(self._get_subversion_properties_options_group())
238 parser.add_option_group(self._get_extraction_options_group())
239 parser.add_option_group(self._get_environment_options_group())
240 parser.add_option_group(self._get_partial_conversion_options_group())
241 parser.add_option_group(self._get_information_options_group())
243 (self.options, self.args) = parser.parse_args(args=self.cmd_args)
245 # Now the log level has been set; log the time when the run started:
246 Log().verbose(
247 time.strftime(
248 'Conversion start time: %Y-%m-%d %I:%M:%S %Z',
249 time.localtime(Log().start_time)
253 if self.options.options_file_found:
254 # Check that no options that are incompatible with --options
255 # were used:
256 self.verify_option_compatibility()
257 else:
258 # --options was not specified. So do the main initialization
259 # based on other command-line options:
260 self.process_options()
262 # Check for problems with the options:
263 self.check_options()
265 def get_description(self):
266 return description
268 def _get_options_file_options_group(self):
269 group = OptionGroup(
270 self.parser, 'Configuration via options file'
272 self.parser.set_default('options_file_found', False)
273 group.add_option(ManOption(
274 '--options', type='string',
275 action='callback', callback=self.callback_options,
276 help=(
277 'read the conversion options from PATH. This '
278 'method allows more flexibility than using '
279 'command-line options. See documentation for info'
281 man_help=(
282 'Read the conversion options from \\fIpath\\fR instead of from '
283 'the command line. This option allows far more conversion '
284 'flexibility than can be achieved using the command-line alone. '
285 'See the documentation for more information. Only the following '
286 'command-line options are allowed in combination with '
287 '\\fB--options\\fR: \\fB-h\\fR/\\fB--help\\fR, '
288 '\\fB--help-passes\\fR, \\fB--version\\fR, '
289 '\\fB-v\\fR/\\fB--verbose\\fR, \\fB-q\\fR/\\fB--quiet\\fR, '
290 '\\fB-p\\fR/\\fB--pass\\fR/\\fB--passes\\fR, \\fB--dry-run\\fR, '
291 '\\fB--profile\\fR, \\fB--sort\\fR, \\fB--trunk-only\\fR, '
292 '\\fB--encoding\\fR, and \\fB--fallback-encoding\\fR. '
293 'Options are processed in the order specified on the command '
294 'line.'
296 metavar='PATH',
298 return group
300 def _get_output_options_group(self):
301 group = OptionGroup(self.parser, 'Output options')
302 return group
304 def _get_conversion_options_group(self):
305 group = OptionGroup(self.parser, 'Conversion options')
306 group.add_option(ContextOption(
307 '--trunk-only',
308 action='store_true',
309 compatible_with_option=True,
310 help='convert only trunk commits, not tags nor branches',
311 man_help=(
312 'Convert only trunk commits, not tags nor branches.'
315 group.add_option(ManOption(
316 '--encoding', type='string',
317 action='callback', callback=self.callback_encoding,
318 help=(
319 'encoding for paths and log messages in CVS repos. '
320 'If option is specified multiple times, encoders '
321 'are tried in order until one succeeds. See '
322 'http://docs.python.org/lib/standard-encodings.html '
323 'for a list of standard Python encodings.'
325 man_help=(
326 'Use \\fIencoding\\fR as the encoding for filenames, log '
327 'messages, and author names in the CVS repos. This option '
328 'may be specified multiple times, in which case the encodings '
329 'are tried in order until one succeeds. Default: ascii. See '
330 'http://docs.python.org/lib/standard-encodings.html for a list '
331 'of other standard encodings.'
333 metavar='ENC',
335 group.add_option(ManOption(
336 '--fallback-encoding', type='string',
337 action='callback', callback=self.callback_fallback_encoding,
338 help='If all --encodings fail, use lossy encoding with ENC',
339 man_help=(
340 'If none of the encodings specified with \\fB--encoding\\fR '
341 'succeed in decoding an author name or log message, then fall '
342 'back to using \\fIencoding\\fR in lossy \'replace\' mode. '
343 'Use of this option may cause information to be lost, but at '
344 'least it allows the conversion to run to completion. This '
345 'option only affects the encoding of log messages and author '
346 'names; there is no fallback encoding for filenames. (By '
347 'using an \\fB--options\\fR file, it is possible to specify '
348 'a fallback encoding for filenames.) Default: disabled.'
350 metavar='ENC',
352 group.add_option(ContextOption(
353 '--retain-conflicting-attic-files',
354 action='store_true',
355 help=(
356 'if a file appears both in and out of '
357 'the CVS Attic, then leave the attic version in a '
358 'SVN directory called "Attic"'
360 man_help=(
361 'If a file appears both inside an outside of the CVS attic, '
362 'retain the attic version in an SVN subdirectory called '
363 '\'Attic\'. (Normally this situation is treated as a fatal '
364 'error.)'
368 return group
370 def _get_symbol_handling_options_group(self):
371 group = OptionGroup(self.parser, 'Symbol handling')
372 self.parser.set_default('symbol_transforms', [])
373 group.add_option(IncompatibleOption(
374 '--symbol-transform', type='string',
375 action='callback', callback=self.callback_symbol_transform,
376 help=(
377 'transform symbol names from P to S, where P and S '
378 'use Python regexp and reference syntax '
379 'respectively. P must match the whole symbol name'
381 man_help=(
382 'Transform RCS/CVS symbol names before entering them into '
383 'Subversion. \\fIpattern\\fR is a Python regexp pattern that '
384 'is matches against the entire symbol name; \\fIreplacement\\fR '
385 'is a replacement using Python\'s regexp reference syntax. '
386 'You may specify any number of these options; they will be '
387 'applied in the order given on the command line.'
389 metavar='P:S',
391 self.parser.set_default('symbol_strategy_rules', [])
392 group.add_option(IncompatibleOption(
393 '--symbol-hints', type='string',
394 action='callback', callback=self.callback_symbol_hints,
395 help='read symbol conversion hints from PATH',
396 man_help=(
397 'Read symbol conversion hints from \\fIpath\\fR. The format of '
398 '\\fIpath\\fR is the same as the format output by '
399 '\\fB--write-symbol-info\\fR, namely a text file with four '
400 'whitespace-separated columns: \\fIproject-id\\fR, '
401 '\\fIsymbol\\fR, \\fIconversion\\fR, and '
402 '\\fIparent-lod-name\\fR. \\fIproject-id\\fR is the numerical '
403 'ID of the project to which the symbol belongs, counting from '
404 '0. \\fIproject-id\\fR can be set to \'.\' if '
405 'project-specificity is not needed. \\fIsymbol-name\\fR is the '
406 'name of the symbol being specified. \\fIconversion\\fR '
407 'specifies how the symbol should be converted, and can be one '
408 'of the values \'branch\', \'tag\', or \'exclude\'. If '
409 '\\fIconversion\\fR is \'.\', then this rule does not affect '
410 'how the symbol is converted. \\fIparent-lod-name\\fR is the '
411 'name of the symbol from which this symbol should sprout, or '
412 '\'.trunk.\' if the symbol should sprout from trunk. If '
413 '\\fIparent-lod-name\\fR is omitted or \'.\', then this rule '
414 'does not affect the preferred parent of this symbol. The file '
415 'may contain blank lines or comment lines (lines whose first '
416 'non-whitespace character is \'#\').'
418 metavar='PATH',
420 self.parser.set_default('symbol_default', 'heuristic')
421 group.add_option(IncompatibleOption(
422 '--symbol-default', type='choice',
423 choices=['heuristic', 'strict', 'branch', 'tag'],
424 action='store',
425 help=(
426 'specify how ambiguous symbols are converted. '
427 'OPT is "heuristic" (default), "strict", "branch", '
428 'or "tag"'
430 man_help=(
431 'Specify how to convert ambiguous symbols (those that appear in '
432 'the CVS archive as both branches and tags). \\fIopt\\fR must '
433 'be \'heuristic\' (decide how to treat each ambiguous symbol '
434 'based on whether it was used more often as a branch/tag in '
435 'CVS), \'strict\' (no default; every ambiguous symbol has to be '
436 'resolved manually using \\fB--force-branch\\fR, '
437 '\\fB--force-tag\\fR, or \\fB--exclude\\fR), \'branch\' (treat '
438 'every ambiguous symbol as a branch), or \'tag\' (treat every '
439 'ambiguous symbol as a tag). The default is \'heuristic\'.'
441 metavar='OPT',
443 group.add_option(IncompatibleOption(
444 '--force-branch', type='string',
445 action='callback', callback=self.callback_force_branch,
446 help='force symbols matching REGEXP to be branches',
447 man_help=(
448 'Force symbols whose names match \\fIregexp\\fR to be branches. '
449 '\\fIregexp\\fR must match the whole symbol name.'
451 metavar='REGEXP',
453 group.add_option(IncompatibleOption(
454 '--force-tag', type='string',
455 action='callback', callback=self.callback_force_tag,
456 help='force symbols matching REGEXP to be tags',
457 man_help=(
458 'Force symbols whose names match \\fIregexp\\fR to be tags. '
459 '\\fIregexp\\fR must match the whole symbol name.'
461 metavar='REGEXP',
463 group.add_option(IncompatibleOption(
464 '--exclude', type='string',
465 action='callback', callback=self.callback_exclude,
466 help='exclude branches and tags matching REGEXP',
467 man_help=(
468 'Exclude branches and tags whose names match \\fIregexp\\fR '
469 'from the conversion. \\fIregexp\\fR must match the whole '
470 'symbol name.'
472 metavar='REGEXP',
474 self.parser.set_default('keep_trivial_imports', False)
475 group.add_option(IncompatibleOption(
476 '--keep-trivial-imports',
477 action='store_true',
478 help=(
479 'do not exclude branches that were only used for '
480 'a single import (usually these are unneeded)'
482 man_help=(
483 'Do not exclude branches that were only used for a single '
484 'import. (By default such branches are excluded because they '
485 'are usually created by the inappropriate use of \\fBcvs '
486 'import\\fR.)'
490 return group
492 def _get_subversion_properties_options_group(self):
493 group = OptionGroup(self.parser, 'Subversion properties')
494 group.add_option(ContextOption(
495 '--username', type='string',
496 action='store',
497 help='username for cvs2svn-synthesized commits',
498 man_help=(
499 'Set the default username to \\fIname\\fR when cvs2svn needs '
500 'to generate a commit for which CVS does not record the '
501 'original username. This happens when a branch or tag is '
502 'created. The default is to use no author at all for such '
503 'commits.'
505 metavar='NAME',
507 self.parser.set_default('auto_props_files', [])
508 group.add_option(IncompatibleOption(
509 '--auto-props', type='string',
510 action='append', dest='auto_props_files',
511 help=(
512 'set file properties from the auto-props section '
513 'of a file in svn config format'
515 man_help=(
516 'Specify a file in the format of Subversion\'s config file, '
517 'whose [auto-props] section can be used to set arbitrary '
518 'properties on files in the Subversion repository based on '
519 'their filenames. (The [auto-props] section header must be '
520 'present; other sections of the config file, including the '
521 'enable-auto-props setting, are ignored.) Filenames are matched '
522 'to the filename patterns case-insensitively.'
525 metavar='FILE',
527 self.parser.set_default('mime_types_files', [])
528 group.add_option(IncompatibleOption(
529 '--mime-types', type='string',
530 action='append', dest='mime_types_files',
531 help=(
532 'specify an apache-style mime.types file for setting '
533 'svn:mime-type'
535 man_help=(
536 'Specify an apache-style mime.types \\fIfile\\fR for setting '
537 'svn:mime-type.'
539 metavar='FILE',
541 self.parser.set_default('eol_from_mime_type', False)
542 group.add_option(IncompatibleOption(
543 '--eol-from-mime-type',
544 action='store_true',
545 help='set svn:eol-style from mime type if known',
546 man_help=(
547 'For files that don\'t have the kb expansion mode but have a '
548 'known mime type, set the eol-style based on the mime type. '
549 'For such files, set svn:eol-style to "native" if the mime type '
550 'begins with "text/", and leave it unset (i.e., no EOL '
551 'translation) otherwise. Files with unknown mime types are '
552 'not affected by this option. This option has no effect '
553 'unless the \\fB--mime-types\\fR option is also specified.'
556 group.add_option(IncompatibleOption(
557 '--default-eol', type='choice',
558 choices=['binary', 'native', 'CRLF', 'LF', 'CR'],
559 action='store',
560 help=(
561 'default svn:eol-style for non-binary files with '
562 'undetermined mime types. STYLE is "binary" '
563 '(default), "native", "CRLF", "LF", or "CR"'
565 man_help=(
566 'Set svn:eol-style to \\fIstyle\\fR for files that don\'t have '
567 'the CVS \'kb\' expansion mode and whose end-of-line '
568 'translation mode hasn\'t been determined by one of the other '
569 'options. \\fIstyle\\fR must be \'binary\' (default), '
570 '\'native\', \'CRLF\', \'LF\', or \'CR\'.'
572 metavar='STYLE',
574 self.parser.set_default('keywords_off', False)
575 group.add_option(IncompatibleOption(
576 '--keywords-off',
577 action='store_true',
578 help=(
579 'don\'t set svn:keywords on any files (by default, '
580 'cvs2svn sets svn:keywords on non-binary files to "%s")'
581 % (config.SVN_KEYWORDS_VALUE,)
583 man_help=(
584 'By default, cvs2svn sets svn:keywords on CVS files to "author '
585 'id date" if the mode of the RCS file in question is either kv, '
586 'kvl or unset. If you use the --keywords-off switch, cvs2svn '
587 'will not set svn:keywords for any file. While this will not '
588 'touch the keywords in the contents of your files, Subversion '
589 'will not expand them.'
592 group.add_option(ContextOption(
593 '--keep-cvsignore',
594 action='store_true',
595 help=(
596 'keep .cvsignore files (in addition to creating '
597 'the analogous svn:ignore properties)'
599 man_help=(
600 'Include \\fI.cvsignore\\fR files in the output. (Normally '
601 'they are unneeded because cvs2svn sets the corresponding '
602 '\\fIsvn:ignore\\fR properties.)'
605 group.add_option(IncompatibleOption(
606 '--cvs-revnums',
607 action='callback', callback=self.callback_cvs_revnums,
608 help='record CVS revision numbers as file properties',
609 man_help=(
610 'Record CVS revision numbers as file properties in the '
611 'Subversion repository. (Note that unless it is removed '
612 'explicitly, the last CVS revision number will remain '
613 'associated with the file even after the file is changed '
614 'within Subversion.)'
618 # Deprecated options:
619 group.add_option(IncompatibleOption(
620 '--no-default-eol',
621 action='store_const', dest='default_eol', const=None,
622 help=optparse.SUPPRESS_HELP,
623 man_help=optparse.SUPPRESS_HELP,
625 self.parser.set_default('auto_props_ignore_case', True)
626 # True is the default now, so this option has no effect:
627 group.add_option(IncompatibleOption(
628 '--auto-props-ignore-case',
629 action='store_true',
630 help=optparse.SUPPRESS_HELP,
631 man_help=optparse.SUPPRESS_HELP,
634 return group
636 def _get_extraction_options_group(self):
637 group = OptionGroup(self.parser, 'Extraction options')
639 return group
641 def _add_use_internal_co_option(self, group):
642 self.parser.set_default('use_internal_co', False)
643 group.add_option(IncompatibleOption(
644 '--use-internal-co',
645 action='store_true',
646 help=(
647 'use internal code to extract revision contents '
648 '(fastest but disk space intensive) (default)'
650 man_help=(
651 'Use internal code to extract revision contents. This '
652 'is up to 50% faster than using \\fB--use-rcs\\fR, but needs '
653 'a lot of disk space: roughly the size of your CVS repository '
654 'plus the peak size of a complete checkout of the repository '
655 'with all branches that existed and still had commits pending '
656 'at a given time. This option is the default.'
660 def _add_use_cvs_option(self, group):
661 self.parser.set_default('use_cvs', False)
662 group.add_option(IncompatibleOption(
663 '--use-cvs',
664 action='store_true',
665 help=(
666 'use CVS to extract revision contents (slower than '
667 '--use-internal-co or --use-rcs)'
669 man_help=(
670 'Use CVS to extract revision contents. This option is slower '
671 'than \\fB--use-internal-co\\fR or \\fB--use-rcs\\fR.'
675 def _add_use_rcs_option(self, group):
676 self.parser.set_default('use_rcs', False)
677 group.add_option(IncompatibleOption(
678 '--use-rcs',
679 action='store_true',
680 help=(
681 'use RCS to extract revision contents (faster than '
682 '--use-cvs but fails in some cases)'
684 man_help=(
685 'Use RCS \'co\' to extract revision contents. This option is '
686 'faster than \\fB--use-cvs\\fR but fails in some cases.'
690 def _get_environment_options_group(self):
691 group = OptionGroup(self.parser, 'Environment options')
692 group.add_option(ContextOption(
693 '--tmpdir', type='string',
694 action='store',
695 help=(
696 'directory to use for temporary data files '
697 '(default "cvs2svn-tmp")'
699 man_help=(
700 'Set the \\fIpath\\fR to use for temporary data. Default '
701 'is a directory called \\fIcvs2svn-tmp\\fR under the current '
702 'directory.'
704 metavar='PATH',
706 self.parser.set_default('co_executable', config.CO_EXECUTABLE)
707 group.add_option(IncompatibleOption(
708 '--co', type='string',
709 action='store', dest='co_executable',
710 help='path to the "co" program (required if --use-rcs)',
711 man_help=(
712 'Path to the \\fIco\\fR program. (\\fIco\\fR is needed if the '
713 '\\fB--use-rcs\\fR option is used.)'
715 metavar='PATH',
717 self.parser.set_default('cvs_executable', config.CVS_EXECUTABLE)
718 group.add_option(IncompatibleOption(
719 '--cvs', type='string',
720 action='store', dest='cvs_executable',
721 help='path to the "cvs" program (required if --use-cvs)',
722 man_help=(
723 'Path to the \\fIcvs\\fR program. (\\fIcvs\\fR is needed if the '
724 '\\fB--use-cvs\\fR option is used.)'
726 metavar='PATH',
728 group.add_option(ContextOption(
729 '--sort', type='string',
730 action='store', dest='sort_executable',
731 compatible_with_option=True,
732 help='path to the GNU "sort" program',
733 man_help=(
734 'Path to the GNU \\fIsort\\fR program. (cvs2svn requires GNU '
735 'sort.)'
737 metavar='PATH',
740 return group
742 def _get_partial_conversion_options_group(self):
743 group = OptionGroup(self.parser, 'Partial conversions')
744 group.add_option(ManOption(
745 '--pass', type='string',
746 action='callback', callback=self.callback_passes,
747 help='execute only specified PASS of conversion',
748 man_help=(
749 'Execute only pass \\fIpass\\fR of the conversion. '
750 '\\fIpass\\fR can be specified by name or by number (see '
751 '\\fB--help-passes\\fR).'
753 metavar='PASS',
755 group.add_option(ManOption(
756 '--passes', '-p', type='string',
757 action='callback', callback=self.callback_passes,
758 help=(
759 'execute passes START through END, inclusive (PASS, '
760 'START, and END can be pass names or numbers)'
762 man_help=(
763 'Execute passes \\fIstart\\fR through \\fIend\\fR of the '
764 'conversion (inclusive). \\fIstart\\fR and \\fIend\\fR can be '
765 'specified by name or by number (see \\fB--help-passes\\fR). '
766 'If \\fIstart\\fR or \\fIend\\fR is missing, it defaults to '
767 'the first or last pass, respectively. For this to work the '
768 'earlier passes must have been completed before on the '
769 'same CVS repository, and the generated data files must be '
770 'in the temporary directory (see \\fB--tmpdir\\fR).'
772 metavar='[START]:[END]',
775 return group
777 def _get_information_options_group(self):
778 group = OptionGroup(self.parser, 'Information options')
779 group.add_option(ManOption(
780 '--version',
781 action='callback', callback=self.callback_version,
782 help='print the version number',
783 man_help='Print the version number.',
785 group.add_option(ManOption(
786 '--help', '-h',
787 action="help",
788 help='print this usage message and exit with success',
789 man_help='Print the usage message and exit with success.',
791 group.add_option(ManOption(
792 '--help-passes',
793 action='callback', callback=self.callback_help_passes,
794 help='list the available passes and their numbers',
795 man_help=(
796 'Print the numbers and names of the conversion passes and '
797 'exit with success.'
800 group.add_option(ManOption(
801 '--man',
802 action='callback', callback=self.callback_manpage,
803 help='write the manpage for this program to standard output',
804 man_help=(
805 'Output the unix-style manpage for this program to standard '
806 'output.'
809 group.add_option(ManOption(
810 '--verbose', '-v',
811 action='callback', callback=self.callback_verbose,
812 help='verbose (may be specified twice for debug output)',
813 man_help=(
814 'Print more information while running. This option may be '
815 'specified twice to output voluminous debugging information.'
818 group.add_option(ManOption(
819 '--quiet', '-q',
820 action='callback', callback=self.callback_quiet,
821 help='quiet (may be specified twice for very quiet)',
822 man_help=(
823 'Print less information while running. This option may be '
824 'specified twice to suppress all non-error output.'
827 group.add_option(ContextOption(
828 '--write-symbol-info', type='string',
829 action='store', dest='symbol_info_filename',
830 help='write information and statistics about CVS symbols to PATH.',
831 man_help=(
832 'Write to \\fIpath\\fR symbol statistics and information about '
833 'how symbols were converted during CollateSymbolsPass.'
835 metavar='PATH',
837 group.add_option(ContextOption(
838 '--skip-cleanup',
839 action='store_true',
840 help='prevent the deletion of intermediate files',
841 man_help='Prevent the deletion of temporary files.',
843 group.add_option(ManOption(
844 '--profile',
845 action='callback', callback=self.callback_profile,
846 help='profile with \'hotshot\' (into file cvs2svn.hotshot)',
847 man_help=(
848 'Profile with \'hotshot\' (into file \\fIcvs2svn.hotshot\\fR).'
852 return group
854 def callback_options(self, option, opt_str, value, parser):
855 parser.values.options_file_found = True
856 self.process_options_file(value)
858 def callback_encoding(self, option, opt_str, value, parser):
859 ctx = Ctx()
861 try:
862 ctx.cvs_author_decoder.add_encoding(value)
863 ctx.cvs_log_decoder.add_encoding(value)
864 ctx.cvs_filename_decoder.add_encoding(value)
865 except LookupError, e:
866 raise FatalError(str(e))
868 def callback_fallback_encoding(self, option, opt_str, value, parser):
869 ctx = Ctx()
871 try:
872 ctx.cvs_author_decoder.set_fallback_encoding(value)
873 ctx.cvs_log_decoder.set_fallback_encoding(value)
874 # Don't use fallback_encoding for filenames.
875 except LookupError, e:
876 raise FatalError(str(e))
878 def callback_help_passes(self, option, opt_str, value, parser):
879 self.pass_manager.help_passes()
880 sys.exit(0)
882 def callback_manpage(self, option, opt_str, value, parser):
883 f = codecs.getwriter('utf_8')(sys.stdout)
884 writer = ManWriter(parser,
885 section='1',
886 date=datetime.date.today(),
887 source='Version %s' % (VERSION,),
888 manual='User Commands',
889 short_desc=self.short_desc,
890 synopsis=self.synopsis,
891 long_desc=self.long_desc,
892 files=self.files,
893 authors=self.authors,
894 see_also=self.see_also)
895 writer.write_manpage(f)
896 sys.exit(0)
898 def callback_version(self, option, opt_str, value, parser):
899 sys.stdout.write(
900 '%s version %s\n' % (self.progname, VERSION)
902 sys.exit(0)
904 def callback_verbose(self, option, opt_str, value, parser):
905 Log().increase_verbosity()
907 def callback_quiet(self, option, opt_str, value, parser):
908 Log().decrease_verbosity()
910 def callback_passes(self, option, opt_str, value, parser):
911 if value.find(':') >= 0:
912 start_pass, end_pass = value.split(':')
913 self.start_pass = self.pass_manager.get_pass_number(start_pass, 1)
914 self.end_pass = self.pass_manager.get_pass_number(
915 end_pass, self.pass_manager.num_passes
917 else:
918 self.end_pass = \
919 self.start_pass = \
920 self.pass_manager.get_pass_number(value)
922 def callback_profile(self, option, opt_str, value, parser):
923 self.profiling = True
925 def callback_symbol_hints(self, option, opt_str, value, parser):
926 parser.values.symbol_strategy_rules.append(SymbolHintsFileRule(value))
928 def callback_force_branch(self, option, opt_str, value, parser):
929 parser.values.symbol_strategy_rules.append(
930 ForceBranchRegexpStrategyRule(value)
933 def callback_force_tag(self, option, opt_str, value, parser):
934 parser.values.symbol_strategy_rules.append(
935 ForceTagRegexpStrategyRule(value)
938 def callback_exclude(self, option, opt_str, value, parser):
939 parser.values.symbol_strategy_rules.append(
940 ExcludeRegexpStrategyRule(value)
943 def callback_cvs_revnums(self, option, opt_str, value, parser):
944 Ctx().svn_property_setters.append(CVSRevisionNumberSetter())
946 def callback_symbol_transform(self, option, opt_str, value, parser):
947 [pattern, replacement] = value.split(":")
948 try:
949 parser.values.symbol_transforms.append(
950 RegexpSymbolTransform(pattern, replacement)
952 except re.error:
953 raise FatalError("'%s' is not a valid regexp." % (pattern,))
955 def process_symbol_strategy_options(self):
956 """Process symbol strategy-related options."""
958 ctx = Ctx()
959 options = self.options
961 # Add the standard symbol name cleanup rules:
962 self.options.symbol_transforms.extend([
963 ReplaceSubstringsSymbolTransform('\\','/'),
964 # Remove leading, trailing, and repeated slashes:
965 NormalizePathsSymbolTransform(),
968 if ctx.trunk_only:
969 if options.symbol_strategy_rules or options.keep_trivial_imports:
970 raise SymbolOptionsWithTrunkOnlyException()
972 else:
973 if not options.keep_trivial_imports:
974 options.symbol_strategy_rules.append(ExcludeTrivialImportBranchRule())
976 options.symbol_strategy_rules.append(UnambiguousUsageRule())
977 if options.symbol_default == 'strict':
978 pass
979 elif options.symbol_default == 'branch':
980 options.symbol_strategy_rules.append(AllBranchRule())
981 elif options.symbol_default == 'tag':
982 options.symbol_strategy_rules.append(AllTagRule())
983 elif options.symbol_default == 'heuristic':
984 options.symbol_strategy_rules.append(BranchIfCommitsRule())
985 options.symbol_strategy_rules.append(HeuristicStrategyRule())
986 else:
987 assert False
989 # Now add a rule whose job it is to pick the preferred parents of
990 # branches and tags:
991 options.symbol_strategy_rules.append(HeuristicPreferredParentRule())
993 def process_property_setter_options(self):
994 """Process the options that set SVN properties."""
996 ctx = Ctx()
997 options = self.options
999 for value in options.auto_props_files:
1000 ctx.svn_property_setters.append(
1001 AutoPropsPropertySetter(value, options.auto_props_ignore_case)
1004 for value in options.mime_types_files:
1005 ctx.svn_property_setters.append(MimeMapper(value))
1007 ctx.svn_property_setters.append(CVSBinaryFileEOLStyleSetter())
1009 ctx.svn_property_setters.append(CVSBinaryFileDefaultMimeTypeSetter())
1011 if options.eol_from_mime_type:
1012 ctx.svn_property_setters.append(EOLStyleFromMimeTypeSetter())
1014 ctx.svn_property_setters.append(
1015 DefaultEOLStyleSetter(options.default_eol)
1018 ctx.svn_property_setters.append(SVNBinaryFileKeywordsPropertySetter())
1020 if not options.keywords_off:
1021 ctx.svn_property_setters.append(
1022 KeywordsPropertySetter(config.SVN_KEYWORDS_VALUE))
1024 ctx.svn_property_setters.append(ExecutablePropertySetter())
1026 def process_options(self):
1027 """Do the main configuration based on command-line options.
1029 This method is only called if the --options option was not
1030 specified."""
1032 raise NotImplementedError()
1034 def check_options(self):
1035 """Check the the run options are OK.
1037 This should only be called after all options have been processed."""
1039 # Convenience var, so we don't have to keep instantiating this Borg.
1040 ctx = Ctx()
1042 if not self.start_pass <= self.end_pass:
1043 raise InvalidPassError(
1044 'Ending pass must not come before starting pass.')
1046 if not ctx.dry_run and ctx.output_option is None:
1047 raise FatalError('No output option specified.')
1049 if ctx.output_option is not None:
1050 ctx.output_option.check()
1052 if not self.projects:
1053 raise FatalError('No project specified.')
1055 def verify_option_compatibility(self):
1056 """Verify that no options incompatible with --options were used.
1058 The --options option was specified. Verify that no incompatible
1059 options or arguments were specified."""
1061 if self.options.options_incompatible_options or self.args:
1062 if self.options.options_incompatible_options:
1063 oio = self.options.options_incompatible_options
1064 Log().error(
1065 '%s: The following options cannot be used in combination with '
1066 'the --options\n'
1067 'option:\n'
1068 ' %s\n'
1069 % (error_prefix, '\n '.join(oio))
1071 if self.args:
1072 Log().error(
1073 '%s: No cvs-repos-path arguments are allowed with the --options '
1074 'option.\n'
1075 % (error_prefix,)
1077 sys.exit(1)
1079 def process_options_file(self, options_filename):
1080 """Read options from the file named OPTIONS_FILENAME.
1082 Store the run options to SELF."""
1084 g = {
1085 'ctx' : Ctx(),
1086 'run_options' : self,
1088 execfile(options_filename, g)
1090 def usage(self):
1091 self.parser.print_help()