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