cvs2git: Make the --blobfile argument optional.
[cvs2svn.git] / cvs2svn_lib / run_options.py
blob853bfcf4e36541371af8890a7c31fd377c15ea7c
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"""
74 description="""\
75 Convert a CVS repository into a Subversion repository, including history.
76 """
79 class IncompatibleOption(ManOption):
80 """A ManOption that is incompatible with the --options option.
82 Record that the option was used so that error checking can later be
83 done."""
85 def __init__(self, *args, **kw):
86 ManOption.__init__(self, *args, **kw)
88 def take_action(self, action, dest, opt, value, values, parser):
89 oio = parser.values.options_incompatible_options
90 if opt not in oio:
91 oio.append(opt)
92 return ManOption.take_action(
93 self, action, dest, opt, value, values, parser
97 class ContextOption(ManOption):
98 """A ManOption that stores its value to Ctx."""
100 def __init__(self, *args, **kw):
101 if kw.get('action') not in self.STORE_ACTIONS:
102 raise ValueError('Invalid action: %s' % (kw['action'],))
104 self.__compatible_with_option = kw.pop('compatible_with_option', False)
105 self.__action = kw.pop('action')
106 try:
107 self.__dest = kw.pop('dest')
108 except KeyError:
109 opt = args[0]
110 if not opt.startswith('--'):
111 raise ValueError()
112 self.__dest = opt[2:].replace('-', '_')
113 if 'const' in kw:
114 self.__const = kw.pop('const')
116 kw['action'] = 'callback'
117 kw['callback'] = self.__callback
119 ManOption.__init__(self, *args, **kw)
121 def __callback(self, option, opt_str, value, parser):
122 if not self.__compatible_with_option:
123 oio = parser.values.options_incompatible_options
124 if opt_str not in oio:
125 oio.append(opt_str)
127 action = self.__action
128 dest = self.__dest
130 if action == "store":
131 setattr(Ctx(), dest, value)
132 elif action == "store_const":
133 setattr(Ctx(), dest, self.__const)
134 elif action == "store_true":
135 setattr(Ctx(), dest, True)
136 elif action == "store_false":
137 setattr(Ctx(), dest, False)
138 elif action == "append":
139 getattr(Ctx(), dest).append(value)
140 elif action == "count":
141 setattr(Ctx(), dest, getattr(Ctx(), dest, 0) + 1)
142 else:
143 raise RuntimeError("unknown action %r" % self.__action)
145 return 1
148 class IncompatibleOptionsException(FatalError):
149 pass
152 # Options that are not allowed to be used with --trunk-only:
153 SYMBOL_OPTIONS = [
154 '--symbol-transform',
155 '--symbol-hints',
156 '--force-branch',
157 '--force-tag',
158 '--exclude',
159 '--keep-trivial-imports',
160 '--symbol-default',
161 '--no-cross-branch-commits',
164 class SymbolOptionsWithTrunkOnlyException(IncompatibleOptionsException):
165 def __init__(self):
166 IncompatibleOptionsException.__init__(
167 self,
168 'The following symbol-related options cannot be used together\n'
169 'with --trunk-only:\n'
170 ' %s'
171 % ('\n '.join(SYMBOL_OPTIONS),)
175 def not_both(opt1val, opt1name, opt2val, opt2name):
176 """Raise an exception if both opt1val and opt2val are set."""
177 if opt1val and opt2val:
178 raise IncompatibleOptionsException(
179 "cannot pass both '%s' and '%s'." % (opt1name, opt2name,)
183 class RunOptions(object):
184 """A place to store meta-options that are used to start the conversion."""
186 # Components of the man page. Attributes set to None here must be set
187 # by subclasses; others may be overridden/augmented by subclasses if
188 # they wish.
189 short_desc = None
190 synopsis = None
191 long_desc = None
192 files = None
193 authors = [
194 u"C. Michael Pilato <cmpilato@collab.net>",
195 u"Greg Stein <gstein@lyra.org>",
196 u"Branko \u010cibej <brane@xbc.nu>",
197 u"Blair Zajac <blair@orcaware.com>",
198 u"Max Bowsher <maxb@ukf.net>",
199 u"Brian Fitzpatrick <fitz@red-bean.com>",
200 u"Tobias Ringstr\u00f6m <tobias@ringstrom.mine.nu>",
201 u"Karl Fogel <kfogel@collab.net>",
202 u"Erik H\u00fclsmann <e.huelsmann@gmx.net>",
203 u"David Summers <david@summersoft.fay.ar.us>",
204 u"Michael Haggerty <mhagger@alum.mit.edu>",
206 see_also = None
208 def __init__(self, progname, cmd_args, pass_manager):
209 """Process the command-line options, storing run options to SELF.
211 PROGNAME is the name of the program, used in the usage string.
212 CMD_ARGS is the list of command-line arguments passed to the
213 program. PASS_MANAGER is an instance of PassManager, needed to
214 help process the -p and --help-passes options."""
216 self.progname = progname
217 self.cmd_args = cmd_args
218 self.pass_manager = pass_manager
219 self.start_pass = 1
220 self.end_pass = self.pass_manager.num_passes
221 self.profiling = False
223 self.projects = []
225 # A list of one list of SymbolStrategyRules for each project:
226 self.project_symbol_strategy_rules = []
228 parser = self.parser = optparse.OptionParser(
229 usage=usage,
230 description=self.get_description(),
231 add_help_option=False,
233 # A place to record any options used that are incompatible with
234 # --options:
235 parser.set_default('options_incompatible_options', [])
237 # Populate the options parser with the options, one group at a
238 # time:
239 parser.add_option_group(self._get_options_file_options_group())
240 parser.add_option_group(self._get_output_options_group())
241 parser.add_option_group(self._get_conversion_options_group())
242 parser.add_option_group(self._get_symbol_handling_options_group())
243 parser.add_option_group(self._get_subversion_properties_options_group())
244 parser.add_option_group(self._get_extraction_options_group())
245 parser.add_option_group(self._get_environment_options_group())
246 parser.add_option_group(self._get_partial_conversion_options_group())
247 parser.add_option_group(self._get_information_options_group())
249 (self.options, self.args) = parser.parse_args(args=self.cmd_args)
251 # Now the log level has been set; log the time when the run started:
252 logger.verbose(
253 time.strftime(
254 'Conversion start time: %Y-%m-%d %I:%M:%S %Z',
255 time.localtime(logger.start_time)
259 if self.options.options_file_found:
260 # Check that no options that are incompatible with --options
261 # were used:
262 self.verify_option_compatibility()
263 else:
264 # --options was not specified. So do the main initialization
265 # based on other command-line options:
266 self.process_options()
268 # Check for problems with the options:
269 self.check_options()
271 def get_description(self):
272 return description
274 def _get_options_file_options_group(self):
275 group = OptionGroup(
276 self.parser, 'Configuration via options file'
278 self.parser.set_default('options_file_found', False)
279 group.add_option(ManOption(
280 '--options', type='string',
281 action='callback', callback=self.callback_options,
282 help=(
283 'read the conversion options from PATH. This '
284 'method allows more flexibility than using '
285 'command-line options. See documentation for info'
287 man_help=(
288 'Read the conversion options from \\fIpath\\fR instead of from '
289 'the command line. This option allows far more conversion '
290 'flexibility than can be achieved using the command-line alone. '
291 'See the documentation for more information. Only the following '
292 'command-line options are allowed in combination with '
293 '\\fB--options\\fR: \\fB-h\\fR/\\fB--help\\fR, '
294 '\\fB--help-passes\\fR, \\fB--version\\fR, '
295 '\\fB-v\\fR/\\fB--verbose\\fR, \\fB-q\\fR/\\fB--quiet\\fR, '
296 '\\fB-p\\fR/\\fB--pass\\fR/\\fB--passes\\fR, \\fB--dry-run\\fR, '
297 '\\fB--profile\\fR, \\fB--trunk-only\\fR, \\fB--encoding\\fR, '
298 'and \\fB--fallback-encoding\\fR. '
299 'Options are processed in the order specified on the command '
300 'line.'
302 metavar='PATH',
304 return group
306 def _get_output_options_group(self):
307 group = OptionGroup(self.parser, 'Output options')
308 return group
310 def _get_conversion_options_group(self):
311 group = OptionGroup(self.parser, 'Conversion options')
312 group.add_option(ContextOption(
313 '--trunk-only',
314 action='store_true',
315 compatible_with_option=True,
316 help='convert only trunk commits, not tags nor branches',
317 man_help=(
318 'Convert only trunk commits, not tags nor branches.'
321 group.add_option(ManOption(
322 '--encoding', type='string',
323 action='callback', callback=self.callback_encoding,
324 help=(
325 'encoding for paths and log messages in CVS repos. '
326 'If option is specified multiple times, encoders '
327 'are tried in order until one succeeds. See '
328 'http://docs.python.org/lib/standard-encodings.html '
329 'for a list of standard Python encodings.'
331 man_help=(
332 'Use \\fIencoding\\fR as the encoding for filenames, log '
333 'messages, and author names in the CVS repos. This option '
334 'may be specified multiple times, in which case the encodings '
335 'are tried in order until one succeeds. Default: ascii. See '
336 'http://docs.python.org/lib/standard-encodings.html for a list '
337 'of other standard encodings.'
339 metavar='ENC',
341 group.add_option(ManOption(
342 '--fallback-encoding', type='string',
343 action='callback', callback=self.callback_fallback_encoding,
344 help='If all --encodings fail, use lossy encoding with ENC',
345 man_help=(
346 'If none of the encodings specified with \\fB--encoding\\fR '
347 'succeed in decoding an author name or log message, then fall '
348 'back to using \\fIencoding\\fR in lossy \'replace\' mode. '
349 'Use of this option may cause information to be lost, but at '
350 'least it allows the conversion to run to completion. This '
351 'option only affects the encoding of log messages and author '
352 'names; there is no fallback encoding for filenames. (By '
353 'using an \\fB--options\\fR file, it is possible to specify '
354 'a fallback encoding for filenames.) Default: disabled.'
356 metavar='ENC',
358 group.add_option(ContextOption(
359 '--retain-conflicting-attic-files',
360 action='store_true',
361 help=(
362 'if a file appears both in and out of '
363 'the CVS Attic, then leave the attic version in a '
364 'subdirectory called "Attic"'
366 man_help=(
367 'If a file appears both inside and outside of the CVS attic, '
368 'retain the attic version in a subdirectory called '
369 '\'Attic\'. (Normally this situation is treated as a fatal '
370 'error.)'
374 return group
376 def _get_symbol_handling_options_group(self):
377 group = OptionGroup(self.parser, 'Symbol handling')
378 self.parser.set_default('symbol_transforms', [])
379 group.add_option(IncompatibleOption(
380 '--symbol-transform', type='string',
381 action='callback', callback=self.callback_symbol_transform,
382 help=(
383 'transform symbol names from P to S, where P and S '
384 'use Python regexp and reference syntax '
385 'respectively. P must match the whole symbol name'
387 man_help=(
388 'Transform RCS/CVS symbol names before entering them into the '
389 'output history. \\fIpattern\\fR is a Python regexp pattern that '
390 'is matches against the entire symbol name; \\fIreplacement\\fR '
391 'is a replacement using Python\'s regexp reference syntax. '
392 'You may specify any number of these options; they will be '
393 'applied in the order given on the command line.'
395 metavar='P:S',
397 self.parser.set_default('symbol_strategy_rules', [])
398 group.add_option(IncompatibleOption(
399 '--symbol-hints', type='string',
400 action='callback', callback=self.callback_symbol_hints,
401 help='read symbol conversion hints from PATH',
402 man_help=(
403 'Read symbol conversion hints from \\fIpath\\fR. The format of '
404 '\\fIpath\\fR is the same as the format output by '
405 '\\fB--write-symbol-info\\fR, namely a text file with four '
406 'whitespace-separated columns: \\fIproject-id\\fR, '
407 '\\fIsymbol\\fR, \\fIconversion\\fR, and '
408 '\\fIparent-lod-name\\fR. \\fIproject-id\\fR is the numerical '
409 'ID of the project to which the symbol belongs, counting from '
410 '0. \\fIproject-id\\fR can be set to \'.\' if '
411 'project-specificity is not needed. \\fIsymbol-name\\fR is the '
412 'name of the symbol being specified. \\fIconversion\\fR '
413 'specifies how the symbol should be converted, and can be one '
414 'of the values \'branch\', \'tag\', or \'exclude\'. If '
415 '\\fIconversion\\fR is \'.\', then this rule does not affect '
416 'how the symbol is converted. \\fIparent-lod-name\\fR is the '
417 'name of the symbol from which this symbol should sprout, or '
418 '\'.trunk.\' if the symbol should sprout from trunk. If '
419 '\\fIparent-lod-name\\fR is omitted or \'.\', then this rule '
420 'does not affect the preferred parent of this symbol. The file '
421 'may contain blank lines or comment lines (lines whose first '
422 'non-whitespace character is \'#\').'
424 metavar='PATH',
426 self.parser.set_default('symbol_default', 'heuristic')
427 group.add_option(IncompatibleOption(
428 '--symbol-default', type='choice',
429 choices=['heuristic', 'strict', 'branch', 'tag', 'exclude'],
430 action='store',
431 help=(
432 'specify how ambiguous symbols are converted. '
433 'OPT is "heuristic" (default), "strict", "branch", '
434 '"tag" or "exclude"'
436 man_help=(
437 'Specify how to convert ambiguous symbols (those that appear in '
438 'the CVS archive as both branches and tags). \\fIopt\\fR must '
439 'be \'heuristic\' (decide how to treat each ambiguous symbol '
440 'based on whether it was used more often as a branch/tag in '
441 'CVS), \'strict\' (no default; every ambiguous symbol has to be '
442 'resolved manually using \\fB--force-branch\\fR, '
443 '\\fB--force-tag\\fR, or \\fB--exclude\\fR), \'branch\' (treat '
444 'every ambiguous symbol as a branch), \'tag\' (treat every '
445 'ambiguous symbol as a tag), or \'exclude\' (do not convert '
446 'ambiguous symbols). The default is \'heuristic\'.'
448 metavar='OPT',
450 group.add_option(IncompatibleOption(
451 '--force-branch', type='string',
452 action='callback', callback=self.callback_force_branch,
453 help='force symbols matching REGEXP to be branches',
454 man_help=(
455 'Force symbols whose names match \\fIregexp\\fR to be branches. '
456 '\\fIregexp\\fR must match the whole symbol name.'
458 metavar='REGEXP',
460 group.add_option(IncompatibleOption(
461 '--force-tag', type='string',
462 action='callback', callback=self.callback_force_tag,
463 help='force symbols matching REGEXP to be tags',
464 man_help=(
465 'Force symbols whose names match \\fIregexp\\fR to be tags. '
466 '\\fIregexp\\fR must match the whole symbol name.'
468 metavar='REGEXP',
470 group.add_option(IncompatibleOption(
471 '--exclude', type='string',
472 action='callback', callback=self.callback_exclude,
473 help='exclude branches and tags matching REGEXP',
474 man_help=(
475 'Exclude branches and tags whose names match \\fIregexp\\fR '
476 'from the conversion. \\fIregexp\\fR must match the whole '
477 'symbol name.'
479 metavar='REGEXP',
481 self.parser.set_default('keep_trivial_imports', False)
482 group.add_option(IncompatibleOption(
483 '--keep-trivial-imports',
484 action='store_true',
485 help=(
486 'do not exclude branches that were only used for '
487 'a single import (usually these are unneeded)'
489 man_help=(
490 'Do not exclude branches that were only used for a single '
491 'import. (By default such branches are excluded because they '
492 'are usually created by the inappropriate use of \\fBcvs '
493 'import\\fR.)'
497 return group
499 def _get_subversion_properties_options_group(self):
500 group = OptionGroup(self.parser, 'Subversion properties')
502 if self.DEFAULT_USERNAME is None:
503 default = 'The default is to use no author at all for such commits.'
504 else:
505 default = 'Default: "%s".' % (self.DEFAULT_USERNAME,)
507 group.add_option(ContextOption(
508 '--username', type='string', default=self.DEFAULT_USERNAME,
509 action='store',
510 help='username for synthesized commits. ' + default,
511 man_help=(
512 'Set the default username to \\fIname\\fR when this program needs '
513 'to generate a commit for which CVS does not record the '
514 'original username. This happens when a branch or tag is '
515 'created. '
516 + default
518 metavar='NAME',
520 self.parser.set_default('auto_props_files', [])
521 group.add_option(IncompatibleOption(
522 '--auto-props', type='string',
523 action='append', dest='auto_props_files',
524 help=(
525 'set file properties from the auto-props section '
526 'of a file in svn config format'
528 man_help=(
529 'Specify a file in the format of Subversion\'s config file, '
530 'whose [auto-props] section can be used to set arbitrary '
531 'properties on files in the Subversion repository based on '
532 'their filenames. (The [auto-props] section header must be '
533 'present; other sections of the config file, including the '
534 'enable-auto-props setting, are ignored.) Filenames are matched '
535 'to the filename patterns case-insensitively.'
538 metavar='FILE',
540 self.parser.set_default('mime_types_files', [])
541 group.add_option(IncompatibleOption(
542 '--mime-types', type='string',
543 action='append', dest='mime_types_files',
544 help=(
545 'specify an apache-style mime.types file for setting '
546 'svn:mime-type'
548 man_help=(
549 'Specify an apache-style mime.types \\fIfile\\fR for setting '
550 'svn:mime-type.'
552 metavar='FILE',
554 self.parser.set_default('eol_from_mime_type', False)
555 group.add_option(IncompatibleOption(
556 '--eol-from-mime-type',
557 action='store_true',
558 help='set svn:eol-style from mime type if known',
559 man_help=(
560 'For files that don\'t have the kb expansion mode but have a '
561 'known mime type, set the eol-style based on the mime type. '
562 'For such files, set svn:eol-style to "native" if the mime type '
563 'begins with "text/", and leave it unset (i.e., no EOL '
564 'translation) otherwise. Files with unknown mime types are '
565 'not affected by this option. This option has no effect '
566 'unless the \\fB--mime-types\\fR option is also specified.'
569 self.parser.set_default('default_eol', 'binary')
570 group.add_option(IncompatibleOption(
571 '--default-eol', type='choice',
572 choices=['binary', 'native', 'CRLF', 'LF', 'CR'],
573 action='store',
574 help=(
575 'default svn:eol-style for non-binary files with '
576 'undetermined mime types. STYLE is "binary" '
577 '(default), "native", "CRLF", "LF", or "CR"'
579 man_help=(
580 'Set svn:eol-style to \\fIstyle\\fR for files that don\'t have '
581 'the CVS \'kb\' expansion mode and whose end-of-line '
582 'translation mode hasn\'t been determined by one of the other '
583 'options. \\fIstyle\\fR must be \'binary\' (default), '
584 '\'native\', \'CRLF\', \'LF\', or \'CR\'.'
586 metavar='STYLE',
588 self.parser.set_default('keywords_off', False)
589 group.add_option(IncompatibleOption(
590 '--keywords-off',
591 action='store_true',
592 help=(
593 'don\'t set svn:keywords on any files (by default, '
594 'cvs2svn sets svn:keywords on non-binary files to "%s")'
595 % (config.SVN_KEYWORDS_VALUE,)
597 man_help=(
598 'By default, cvs2svn sets svn:keywords on CVS files to "author '
599 'id date" if the mode of the RCS file in question is either kv, '
600 'kvl or unset. If you use the --keywords-off switch, cvs2svn '
601 'will not set svn:keywords for any file. While this will not '
602 'touch the keywords in the contents of your files, Subversion '
603 'will not expand them.'
606 group.add_option(ContextOption(
607 '--keep-cvsignore',
608 action='store_true',
609 help=(
610 'keep .cvsignore files (in addition to creating '
611 'the analogous svn:ignore properties)'
613 man_help=(
614 'Include \\fI.cvsignore\\fR files in the output. (Normally '
615 'they are unneeded because cvs2svn sets the corresponding '
616 '\\fIsvn:ignore\\fR properties.)'
619 group.add_option(IncompatibleOption(
620 '--cvs-revnums',
621 action='callback', callback=self.callback_cvs_revnums,
622 help='record CVS revision numbers as file properties',
623 man_help=(
624 'Record CVS revision numbers as file properties in the '
625 'Subversion repository. (Note that unless it is removed '
626 'explicitly, the last CVS revision number will remain '
627 'associated with the file even after the file is changed '
628 'within Subversion.)'
632 # Deprecated options:
633 group.add_option(IncompatibleOption(
634 '--no-default-eol',
635 action='store_const', dest='default_eol', const=None,
636 help=optparse.SUPPRESS_HELP,
637 man_help=optparse.SUPPRESS_HELP,
639 self.parser.set_default('auto_props_ignore_case', True)
640 # True is the default now, so this option has no effect:
641 group.add_option(IncompatibleOption(
642 '--auto-props-ignore-case',
643 action='store_true',
644 help=optparse.SUPPRESS_HELP,
645 man_help=optparse.SUPPRESS_HELP,
648 return group
650 def _get_extraction_options_group(self):
651 group = OptionGroup(self.parser, 'Extraction options')
653 return group
655 def _add_use_internal_co_option(self, group):
656 self.parser.set_default('use_internal_co', False)
657 group.add_option(IncompatibleOption(
658 '--use-internal-co',
659 action='store_true',
660 help=(
661 'use internal code to extract revision contents '
662 '(fastest but disk space intensive) (default)'
664 man_help=(
665 'Use internal code to extract revision contents. This '
666 'is up to 50% faster than using \\fB--use-rcs\\fR, but needs '
667 'a lot of disk space: roughly the size of your CVS repository '
668 'plus the peak size of a complete checkout of the repository '
669 'with all branches that existed and still had commits pending '
670 'at a given time. This option is the default.'
674 def _add_use_cvs_option(self, group):
675 self.parser.set_default('use_cvs', False)
676 group.add_option(IncompatibleOption(
677 '--use-cvs',
678 action='store_true',
679 help=(
680 'use CVS to extract revision contents (slower than '
681 '--use-internal-co or --use-rcs)'
683 man_help=(
684 'Use CVS to extract revision contents. This option is slower '
685 'than \\fB--use-internal-co\\fR or \\fB--use-rcs\\fR.'
689 def _add_use_rcs_option(self, group):
690 self.parser.set_default('use_rcs', False)
691 group.add_option(IncompatibleOption(
692 '--use-rcs',
693 action='store_true',
694 help=(
695 'use RCS to extract revision contents (faster than '
696 '--use-cvs but fails in some cases)'
698 man_help=(
699 'Use RCS \'co\' to extract revision contents. This option is '
700 'faster than \\fB--use-cvs\\fR but fails in some cases.'
704 def _get_environment_options_group(self):
705 group = OptionGroup(self.parser, 'Environment options')
706 group.add_option(ContextOption(
707 '--tmpdir', type='string',
708 action='store',
709 help=(
710 'directory to use for temporary data files '
711 '(default is to create a temporary subdirectory under %r)'
712 ) % (tempfile.gettempdir(),),
713 man_help=(
714 'Set the \\fIpath\\fR to use for temporary data. The default '
715 'is to create a temporary subdirectory under \\fI%s\\fR.'
716 ) % (tempfile.gettempdir(),),
717 metavar='PATH',
719 self.parser.set_default('co_executable', config.CO_EXECUTABLE)
720 group.add_option(IncompatibleOption(
721 '--co', type='string',
722 action='store', dest='co_executable',
723 help='path to the "co" program (required if --use-rcs)',
724 man_help=(
725 'Path to the \\fIco\\fR program. (\\fIco\\fR is needed if the '
726 '\\fB--use-rcs\\fR option is used.)'
728 metavar='PATH',
730 self.parser.set_default('cvs_executable', config.CVS_EXECUTABLE)
731 group.add_option(IncompatibleOption(
732 '--cvs', type='string',
733 action='store', dest='cvs_executable',
734 help='path to the "cvs" program (required if --use-cvs)',
735 man_help=(
736 'Path to the \\fIcvs\\fR program. (\\fIcvs\\fR is needed if the '
737 '\\fB--use-cvs\\fR option is used.)'
739 metavar='PATH',
742 return group
744 def _get_partial_conversion_options_group(self):
745 group = OptionGroup(self.parser, 'Partial conversions')
746 group.add_option(ManOption(
747 '--pass', type='string',
748 action='callback', callback=self.callback_passes,
749 help='execute only specified PASS of conversion',
750 man_help=(
751 'Execute only pass \\fIpass\\fR of the conversion. '
752 '\\fIpass\\fR can be specified by name or by number (see '
753 '\\fB--help-passes\\fR).'
755 metavar='PASS',
757 group.add_option(ManOption(
758 '--passes', '-p', type='string',
759 action='callback', callback=self.callback_passes,
760 help=(
761 'execute passes START through END, inclusive (PASS, '
762 'START, and END can be pass names or numbers)'
764 man_help=(
765 'Execute passes \\fIstart\\fR through \\fIend\\fR of the '
766 'conversion (inclusive). \\fIstart\\fR and \\fIend\\fR can be '
767 'specified by name or by number (see \\fB--help-passes\\fR). '
768 'If \\fIstart\\fR or \\fIend\\fR is missing, it defaults to '
769 'the first or last pass, respectively. For this to work the '
770 'earlier passes must have been completed before on the '
771 'same CVS repository, and the generated data files must be '
772 'in the temporary directory (see \\fB--tmpdir\\fR).'
774 metavar='[START]:[END]',
777 return group
779 def _get_information_options_group(self):
780 group = OptionGroup(self.parser, 'Information options')
781 group.add_option(ManOption(
782 '--version',
783 action='callback', callback=self.callback_version,
784 help='print the version number',
785 man_help='Print the version number.',
787 group.add_option(ManOption(
788 '--help', '-h',
789 action="help",
790 help='print this usage message and exit with success',
791 man_help='Print the usage message and exit with success.',
793 group.add_option(ManOption(
794 '--help-passes',
795 action='callback', callback=self.callback_help_passes,
796 help='list the available passes and their numbers',
797 man_help=(
798 'Print the numbers and names of the conversion passes and '
799 'exit with success.'
802 group.add_option(ManOption(
803 '--man',
804 action='callback', callback=self.callback_manpage,
805 help='write the manpage for this program to standard output',
806 man_help=(
807 'Output the unix-style manpage for this program to standard '
808 'output.'
811 group.add_option(ManOption(
812 '--verbose', '-v',
813 action='callback', callback=self.callback_verbose,
814 help='verbose (may be specified twice for debug output)',
815 man_help=(
816 'Print more information while running. This option may be '
817 'specified twice to output voluminous debugging information.'
820 group.add_option(ManOption(
821 '--quiet', '-q',
822 action='callback', callback=self.callback_quiet,
823 help='quiet (may be specified twice for very quiet)',
824 man_help=(
825 'Print less information while running. This option may be '
826 'specified twice to suppress all non-error output.'
829 group.add_option(ContextOption(
830 '--write-symbol-info', type='string',
831 action='store', dest='symbol_info_filename',
832 help='write information and statistics about CVS symbols to PATH.',
833 man_help=(
834 'Write to \\fIpath\\fR symbol statistics and information about '
835 'how symbols were converted during CollateSymbolsPass.'
837 metavar='PATH',
839 group.add_option(ContextOption(
840 '--skip-cleanup',
841 action='store_true',
842 help='prevent the deletion of intermediate files',
843 man_help='Prevent the deletion of temporary files.',
845 prof = 'cProfile'
846 try:
847 import cProfile
848 except ImportError:
849 prof = 'hotshot'
850 group.add_option(ManOption(
851 '--profile',
852 action='callback', callback=self.callback_profile,
853 help='profile with \'' + prof + '\' (into file cvs2svn.' + prof + ')',
854 man_help=(
855 'Profile with \'' + prof + '\' (into file \\fIcvs2svn.' + prof + '\\fR).'
859 return group
861 def callback_options(self, option, opt_str, value, parser):
862 parser.values.options_file_found = True
863 self.process_options_file(value)
865 def callback_encoding(self, option, opt_str, value, parser):
866 ctx = Ctx()
868 try:
869 ctx.cvs_author_decoder.add_encoding(value)
870 ctx.cvs_log_decoder.add_encoding(value)
871 ctx.cvs_filename_decoder.add_encoding(value)
872 except LookupError, e:
873 raise FatalError(str(e))
875 def callback_fallback_encoding(self, option, opt_str, value, parser):
876 ctx = Ctx()
878 try:
879 ctx.cvs_author_decoder.set_fallback_encoding(value)
880 ctx.cvs_log_decoder.set_fallback_encoding(value)
881 # Don't use fallback_encoding for filenames.
882 except LookupError, e:
883 raise FatalError(str(e))
885 def callback_help_passes(self, option, opt_str, value, parser):
886 self.pass_manager.help_passes()
887 sys.exit(0)
889 def callback_manpage(self, option, opt_str, value, parser):
890 f = codecs.getwriter('utf_8')(sys.stdout)
891 writer = ManWriter(parser,
892 section='1',
893 date=datetime.date.today(),
894 source='Version %s' % (VERSION,),
895 manual='User Commands',
896 short_desc=self.short_desc,
897 synopsis=self.synopsis,
898 long_desc=self.long_desc,
899 files=self.files,
900 authors=self.authors,
901 see_also=self.see_also)
902 writer.write_manpage(f)
903 sys.exit(0)
905 def callback_version(self, option, opt_str, value, parser):
906 sys.stdout.write(
907 '%s version %s\n' % (self.progname, VERSION)
909 sys.exit(0)
911 def callback_verbose(self, option, opt_str, value, parser):
912 logger.increase_verbosity()
914 def callback_quiet(self, option, opt_str, value, parser):
915 logger.decrease_verbosity()
917 def callback_passes(self, option, opt_str, value, parser):
918 if value.find(':') >= 0:
919 start_pass, end_pass = value.split(':')
920 self.start_pass = self.pass_manager.get_pass_number(start_pass, 1)
921 self.end_pass = self.pass_manager.get_pass_number(
922 end_pass, self.pass_manager.num_passes
924 else:
925 self.end_pass = \
926 self.start_pass = \
927 self.pass_manager.get_pass_number(value)
929 def callback_profile(self, option, opt_str, value, parser):
930 self.profiling = True
932 def callback_symbol_hints(self, option, opt_str, value, parser):
933 parser.values.symbol_strategy_rules.append(SymbolHintsFileRule(value))
935 def callback_force_branch(self, option, opt_str, value, parser):
936 parser.values.symbol_strategy_rules.append(
937 ForceBranchRegexpStrategyRule(value)
940 def callback_force_tag(self, option, opt_str, value, parser):
941 parser.values.symbol_strategy_rules.append(
942 ForceTagRegexpStrategyRule(value)
945 def callback_exclude(self, option, opt_str, value, parser):
946 parser.values.symbol_strategy_rules.append(
947 ExcludeRegexpStrategyRule(value)
950 def callback_cvs_revnums(self, option, opt_str, value, parser):
951 Ctx().revision_property_setters.append(CVSRevisionNumberSetter())
953 def callback_symbol_transform(self, option, opt_str, value, parser):
954 [pattern, replacement] = value.split(":")
955 try:
956 parser.values.symbol_transforms.append(
957 RegexpSymbolTransform(pattern, replacement)
959 except re.error:
960 raise FatalError("'%s' is not a valid regexp." % (pattern,))
962 # Common to SVNRunOptions, HgRunOptions (GitRunOptions and
963 # BzrRunOptions do not support --use-internal-co, so cannot use this).
964 def process_all_extraction_options(self):
965 ctx = Ctx()
966 options = self.options
968 not_both(options.use_rcs, '--use-rcs',
969 options.use_cvs, '--use-cvs')
971 not_both(options.use_rcs, '--use-rcs',
972 options.use_internal_co, '--use-internal-co')
974 not_both(options.use_cvs, '--use-cvs',
975 options.use_internal_co, '--use-internal-co')
977 if options.use_rcs:
978 ctx.revision_collector = NullRevisionCollector()
979 ctx.revision_reader = RCSRevisionReader(options.co_executable)
980 elif options.use_cvs:
981 ctx.revision_collector = NullRevisionCollector()
982 ctx.revision_reader = CVSRevisionReader(options.cvs_executable)
983 else:
984 # --use-internal-co is the default:
985 ctx.revision_collector = InternalRevisionCollector(compress=True)
986 ctx.revision_reader = InternalRevisionReader(compress=True)
988 def process_symbol_strategy_options(self):
989 """Process symbol strategy-related options."""
991 ctx = Ctx()
992 options = self.options
994 # Add the standard symbol name cleanup rules:
995 self.options.symbol_transforms.extend([
996 ReplaceSubstringsSymbolTransform('\\','/'),
997 # Remove leading, trailing, and repeated slashes:
998 NormalizePathsSymbolTransform(),
1001 if ctx.trunk_only:
1002 if options.symbol_strategy_rules or options.keep_trivial_imports:
1003 raise SymbolOptionsWithTrunkOnlyException()
1005 else:
1006 if not options.keep_trivial_imports:
1007 options.symbol_strategy_rules.append(ExcludeTrivialImportBranchRule())
1009 options.symbol_strategy_rules.append(UnambiguousUsageRule())
1010 if options.symbol_default == 'strict':
1011 pass
1012 elif options.symbol_default == 'branch':
1013 options.symbol_strategy_rules.append(AllBranchRule())
1014 elif options.symbol_default == 'tag':
1015 options.symbol_strategy_rules.append(AllTagRule())
1016 elif options.symbol_default == 'heuristic':
1017 options.symbol_strategy_rules.append(BranchIfCommitsRule())
1018 options.symbol_strategy_rules.append(HeuristicStrategyRule())
1019 elif options.symbol_default == 'exclude':
1020 options.symbol_strategy_rules.append(AllExcludedRule())
1021 else:
1022 assert False
1024 # Now add a rule whose job it is to pick the preferred parents of
1025 # branches and tags:
1026 options.symbol_strategy_rules.append(HeuristicPreferredParentRule())
1028 def process_property_setter_options(self):
1029 """Process the options that set SVN properties."""
1031 ctx = Ctx()
1032 options = self.options
1034 for value in options.auto_props_files:
1035 ctx.file_property_setters.append(
1036 AutoPropsPropertySetter(value, options.auto_props_ignore_case)
1039 for value in options.mime_types_files:
1040 ctx.file_property_setters.append(MimeMapper(value))
1042 ctx.file_property_setters.append(CVSBinaryFileEOLStyleSetter())
1044 ctx.file_property_setters.append(CVSBinaryFileDefaultMimeTypeSetter())
1046 if options.eol_from_mime_type:
1047 ctx.file_property_setters.append(EOLStyleFromMimeTypeSetter())
1049 ctx.file_property_setters.append(
1050 DefaultEOLStyleSetter(options.default_eol)
1053 ctx.file_property_setters.append(SVNBinaryFileKeywordsPropertySetter())
1055 if not options.keywords_off:
1056 ctx.file_property_setters.append(
1057 KeywordsPropertySetter(config.SVN_KEYWORDS_VALUE)
1060 ctx.file_property_setters.append(ExecutablePropertySetter())
1062 ctx.file_property_setters.append(DescriptionPropertySetter())
1064 def process_options(self):
1065 """Do the main configuration based on command-line options.
1067 This method is only called if the --options option was not
1068 specified."""
1070 raise NotImplementedError()
1072 def check_options(self):
1073 """Check the the run options are OK.
1075 This should only be called after all options have been processed."""
1077 # Convenience var, so we don't have to keep instantiating this Borg.
1078 ctx = Ctx()
1080 if not self.start_pass <= self.end_pass:
1081 raise InvalidPassError(
1082 'Ending pass must not come before starting pass.')
1084 if not ctx.dry_run and ctx.output_option is None:
1085 raise FatalError('No output option specified.')
1087 if ctx.output_option is not None:
1088 ctx.output_option.check()
1090 if not self.projects:
1091 raise FatalError('No project specified.')
1093 def verify_option_compatibility(self):
1094 """Verify that no options incompatible with --options were used.
1096 The --options option was specified. Verify that no incompatible
1097 options or arguments were specified."""
1099 if self.options.options_incompatible_options or self.args:
1100 if self.options.options_incompatible_options:
1101 oio = self.options.options_incompatible_options
1102 logger.error(
1103 '%s: The following options cannot be used in combination with '
1104 'the --options\n'
1105 'option:\n'
1106 ' %s\n'
1107 % (error_prefix, '\n '.join(oio))
1109 if self.args:
1110 logger.error(
1111 '%s: No cvs-repos-path arguments are allowed with the --options '
1112 'option.\n'
1113 % (error_prefix,)
1115 sys.exit(1)
1117 def process_options_file(self, options_filename):
1118 """Read options from the file named OPTIONS_FILENAME.
1120 Store the run options to SELF."""
1122 g = {
1123 'ctx' : Ctx(),
1124 'run_options' : self,
1126 execfile(options_filename, g)
1128 def usage(self):
1129 self.parser.print_help()