If SOURCE_DATE_EPOCH is set, use it for manpages
[cvs2svn.git] / cvs2svn_lib / run_options.py
blob2721d23207eacb6b4eb83ff3d65f7d103fadef50
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 os
20 import sys
21 import re
22 import optparse
23 from optparse import OptionGroup
24 import datetime
25 import tempfile
26 import codecs
27 import time
29 from cvs2svn_lib.version import VERSION
30 from cvs2svn_lib import config
31 from cvs2svn_lib.common import error_prefix
32 from cvs2svn_lib.common import FatalError
33 from cvs2svn_lib.man_writer import ManWriter
34 from cvs2svn_lib.log import logger
35 from cvs2svn_lib.context import Ctx
36 from cvs2svn_lib.man_writer import ManOption
37 from cvs2svn_lib.pass_manager import InvalidPassError
38 from cvs2svn_lib.revision_manager import NullRevisionCollector
39 from cvs2svn_lib.rcs_revision_manager import RCSRevisionReader
40 from cvs2svn_lib.cvs_revision_manager import CVSRevisionReader
41 from cvs2svn_lib.checkout_internal import InternalRevisionCollector
42 from cvs2svn_lib.checkout_internal import InternalRevisionReader
43 from cvs2svn_lib.symbol_strategy import AllBranchRule
44 from cvs2svn_lib.symbol_strategy import AllExcludedRule
45 from cvs2svn_lib.symbol_strategy import AllTagRule
46 from cvs2svn_lib.symbol_strategy import BranchIfCommitsRule
47 from cvs2svn_lib.symbol_strategy import ExcludeRegexpStrategyRule
48 from cvs2svn_lib.symbol_strategy import ForceBranchRegexpStrategyRule
49 from cvs2svn_lib.symbol_strategy import ForceTagRegexpStrategyRule
50 from cvs2svn_lib.symbol_strategy import ExcludeTrivialImportBranchRule
51 from cvs2svn_lib.symbol_strategy import HeuristicStrategyRule
52 from cvs2svn_lib.symbol_strategy import UnambiguousUsageRule
53 from cvs2svn_lib.symbol_strategy import HeuristicPreferredParentRule
54 from cvs2svn_lib.symbol_strategy import SymbolHintsFileRule
55 from cvs2svn_lib.symbol_transform import ReplaceSubstringsSymbolTransform
56 from cvs2svn_lib.symbol_transform import RegexpSymbolTransform
57 from cvs2svn_lib.symbol_transform import NormalizePathsSymbolTransform
58 from cvs2svn_lib.property_setters import AutoPropsPropertySetter
59 from cvs2svn_lib.property_setters import CVSBinaryFileDefaultMimeTypeSetter
60 from cvs2svn_lib.property_setters import CVSBinaryFileEOLStyleSetter
61 from cvs2svn_lib.property_setters import CVSRevisionNumberSetter
62 from cvs2svn_lib.property_setters import DefaultEOLStyleSetter
63 from cvs2svn_lib.property_setters import EOLStyleFromMimeTypeSetter
64 from cvs2svn_lib.property_setters import ExecutablePropertySetter
65 from cvs2svn_lib.property_setters import DescriptionPropertySetter
66 from cvs2svn_lib.property_setters import KeywordsPropertySetter
67 from cvs2svn_lib.property_setters import MimeMapper
68 from cvs2svn_lib.property_setters import SVNBinaryFileKeywordsPropertySetter
71 usage = """\
72 Usage: %prog --options OPTIONFILE
73 %prog [OPTION...] OUTPUT-OPTION CVS-REPOS-PATH"""
76 class IncompatibleOption(ManOption):
77 """A ManOption that is incompatible with the --options option.
79 Record that the option was used so that error checking can later be
80 done."""
82 def __init__(self, *args, **kw):
83 ManOption.__init__(self, *args, **kw)
85 def take_action(self, action, dest, opt, value, values, parser):
86 oio = parser.values.options_incompatible_options
87 if opt not in oio:
88 oio.append(opt)
89 return ManOption.take_action(
90 self, action, dest, opt, value, values, parser
94 class ContextOption(ManOption):
95 """A ManOption that stores its value to Ctx."""
97 def __init__(self, *args, **kw):
98 if kw.get('action') not in self.STORE_ACTIONS:
99 raise ValueError('Invalid action: %s' % (kw['action'],))
101 self.__compatible_with_option = kw.pop('compatible_with_option', False)
102 self.__action = kw.pop('action')
103 try:
104 self.__dest = kw.pop('dest')
105 except KeyError:
106 opt = args[0]
107 if not opt.startswith('--'):
108 raise ValueError()
109 self.__dest = opt[2:].replace('-', '_')
110 if 'const' in kw:
111 self.__const = kw.pop('const')
113 kw['action'] = 'callback'
114 kw['callback'] = self.__callback
116 ManOption.__init__(self, *args, **kw)
118 def __callback(self, option, opt_str, value, parser):
119 if not self.__compatible_with_option:
120 oio = parser.values.options_incompatible_options
121 if opt_str not in oio:
122 oio.append(opt_str)
124 action = self.__action
125 dest = self.__dest
127 if action == "store":
128 setattr(Ctx(), dest, value)
129 elif action == "store_const":
130 setattr(Ctx(), dest, self.__const)
131 elif action == "store_true":
132 setattr(Ctx(), dest, True)
133 elif action == "store_false":
134 setattr(Ctx(), dest, False)
135 elif action == "append":
136 getattr(Ctx(), dest).append(value)
137 elif action == "count":
138 setattr(Ctx(), dest, getattr(Ctx(), dest, 0) + 1)
139 else:
140 raise RuntimeError("unknown action %r" % self.__action)
142 return 1
145 class IncompatibleOptionsException(FatalError):
146 pass
149 # Options that are not allowed to be used with --trunk-only:
150 SYMBOL_OPTIONS = [
151 '--symbol-transform',
152 '--symbol-hints',
153 '--force-branch',
154 '--force-tag',
155 '--exclude',
156 '--keep-trivial-imports',
157 '--symbol-default',
158 '--no-cross-branch-commits',
161 class SymbolOptionsWithTrunkOnlyException(IncompatibleOptionsException):
162 def __init__(self):
163 IncompatibleOptionsException.__init__(
164 self,
165 'The following symbol-related options cannot be used together\n'
166 'with --trunk-only:\n'
167 ' %s'
168 % ('\n '.join(SYMBOL_OPTIONS),)
172 def not_both(opt1val, opt1name, opt2val, opt2name):
173 """Raise an exception if both opt1val and opt2val are set."""
174 if opt1val and opt2val:
175 raise IncompatibleOptionsException(
176 "cannot pass both '%s' and '%s'." % (opt1name, opt2name,)
180 class RunOptions(object):
181 """A place to store meta-options that are used to start the conversion."""
183 # Components of the man page. Attributes set to None here must be set
184 # by subclasses; others may be overridden/augmented by subclasses if
185 # they wish.
186 short_desc = None
187 synopsis = None
188 long_desc = None
189 files = None
190 authors = [
191 u"C. Michael Pilato <cmpilato@collab.net>",
192 u"Greg Stein <gstein@lyra.org>",
193 u"Branko \u010cibej <brane@xbc.nu>",
194 u"Blair Zajac <blair@orcaware.com>",
195 u"Max Bowsher <maxb@ukf.net>",
196 u"Brian Fitzpatrick <fitz@red-bean.com>",
197 u"Tobias Ringstr\u00f6m <tobias@ringstrom.mine.nu>",
198 u"Karl Fogel <kfogel@collab.net>",
199 u"Erik H\u00fclsmann <e.huelsmann@gmx.net>",
200 u"David Summers <david@summersoft.fay.ar.us>",
201 u"Michael Haggerty <mhagger@alum.mit.edu>",
203 see_also = None
205 def __init__(self, progname, cmd_args, pass_manager):
206 """Process the command-line options, storing run options to SELF.
208 PROGNAME is the name of the program, used in the usage string.
209 CMD_ARGS is the list of command-line arguments passed to the
210 program. PASS_MANAGER is an instance of PassManager, needed to
211 help process the -p and --help-passes options."""
213 self.progname = progname
214 self.cmd_args = cmd_args
215 self.pass_manager = pass_manager
216 self.start_pass = 1
217 self.end_pass = self.pass_manager.num_passes
218 self.profiling = False
220 self.projects = []
222 # A list of one list of SymbolStrategyRules for each project:
223 self.project_symbol_strategy_rules = []
225 parser = self.parser = optparse.OptionParser(
226 usage=usage,
227 description=self.get_description(),
228 add_help_option=False,
230 # A place to record any options used that are incompatible with
231 # --options:
232 parser.set_default('options_incompatible_options', [])
234 # Populate the options parser with the options, one group at a
235 # time:
236 parser.add_option_group(self._get_options_file_options_group())
237 parser.add_option_group(self._get_output_options_group())
238 parser.add_option_group(self._get_conversion_options_group())
239 parser.add_option_group(self._get_symbol_handling_options_group())
240 parser.add_option_group(self._get_subversion_properties_options_group())
241 parser.add_option_group(self._get_extraction_options_group())
242 parser.add_option_group(self._get_environment_options_group())
243 parser.add_option_group(self._get_partial_conversion_options_group())
244 parser.add_option_group(self._get_information_options_group())
246 (self.options, self.args) = parser.parse_args(args=self.cmd_args)
248 # Now the log level has been set; log the time when the run started:
249 logger.verbose(
250 time.strftime(
251 'Conversion start time: %Y-%m-%d %I:%M:%S %Z',
252 time.localtime(logger.start_time)
256 if self.options.options_file_found:
257 # Check that no options that are incompatible with --options
258 # were used:
259 self.verify_option_compatibility()
260 else:
261 # --options was not specified. So do the main initialization
262 # based on other command-line options:
263 self.process_options()
265 # Check for problems with the options:
266 self.check_options()
268 @classmethod
269 def get_description(klass):
270 return klass.description
272 def _get_options_file_options_group(self):
273 group = OptionGroup(
274 self.parser, 'Configuration via options file'
276 self.parser.set_default('options_file_found', False)
277 group.add_option(ManOption(
278 '--options', type='string',
279 action='callback', callback=self.callback_options,
280 help=(
281 'read the conversion options from PATH. This '
282 'method allows more flexibility than using '
283 'command-line options. See documentation for info'
285 man_help=(
286 'Read the conversion options from \\fIpath\\fR instead of from '
287 'the command line. This option allows far more conversion '
288 'flexibility than can be achieved using the command-line alone. '
289 'See the documentation for more information. Only the following '
290 'command-line options are allowed in combination with '
291 '\\fB--options\\fR: \\fB-h\\fR/\\fB--help\\fR, '
292 '\\fB--help-passes\\fR, \\fB--version\\fR, '
293 '\\fB-v\\fR/\\fB--verbose\\fR, \\fB-q\\fR/\\fB--quiet\\fR, '
294 '\\fB-p\\fR/\\fB--pass\\fR/\\fB--passes\\fR, \\fB--dry-run\\fR, '
295 '\\fB--profile\\fR, \\fB--trunk-only\\fR, \\fB--encoding\\fR, '
296 'and \\fB--fallback-encoding\\fR. '
297 'Options are processed in the order specified on the command '
298 'line.'
300 metavar='PATH',
302 return group
304 def _get_output_options_group(self):
305 group = OptionGroup(self.parser, 'Output options')
306 return group
308 def _get_conversion_options_group(self):
309 group = OptionGroup(self.parser, 'Conversion options')
310 group.add_option(ContextOption(
311 '--trunk-only',
312 action='store_true',
313 compatible_with_option=True,
314 help='convert only trunk commits, not tags nor branches',
315 man_help=(
316 'Convert only trunk commits, not tags nor branches.'
319 group.add_option(ManOption(
320 '--encoding', type='string',
321 action='callback', callback=self.callback_encoding,
322 help=(
323 'encoding for paths and log messages in CVS repos. '
324 'If option is specified multiple times, encoders '
325 'are tried in order until one succeeds. See '
326 'http://docs.python.org/lib/standard-encodings.html '
327 'for a list of standard Python encodings.'
329 man_help=(
330 'Use \\fIencoding\\fR as the encoding for filenames, log '
331 'messages, and author names in the CVS repos. This option '
332 'may be specified multiple times, in which case the encodings '
333 'are tried in order until one succeeds. Default: ascii. See '
334 'http://docs.python.org/lib/standard-encodings.html for a list '
335 'of other standard encodings.'
337 metavar='ENC',
339 group.add_option(ManOption(
340 '--fallback-encoding', type='string',
341 action='callback', callback=self.callback_fallback_encoding,
342 help='If all --encodings fail, use lossy encoding with ENC',
343 man_help=(
344 'If none of the encodings specified with \\fB--encoding\\fR '
345 'succeed in decoding an author name or log message, then fall '
346 'back to using \\fIencoding\\fR in lossy \'replace\' mode. '
347 'Use of this option may cause information to be lost, but at '
348 'least it allows the conversion to run to completion. This '
349 'option only affects the encoding of log messages and author '
350 'names; there is no fallback encoding for filenames. (By '
351 'using an \\fB--options\\fR file, it is possible to specify '
352 'a fallback encoding for filenames.) Default: disabled.'
354 metavar='ENC',
356 group.add_option(ContextOption(
357 '--retain-conflicting-attic-files',
358 action='store_true',
359 help=(
360 'if a file appears both in and out of '
361 'the CVS Attic, then leave the attic version in a '
362 'subdirectory called "Attic"'
364 man_help=(
365 'If a file appears both inside and outside of the CVS attic, '
366 'retain the attic version in a subdirectory called '
367 '\'Attic\'. (Normally this situation is treated as a fatal '
368 'error.)'
372 return group
374 def _get_symbol_handling_options_group(self):
375 group = OptionGroup(self.parser, 'Symbol handling')
376 self.parser.set_default('symbol_transforms', [])
377 group.add_option(IncompatibleOption(
378 '--symbol-transform', type='string',
379 action='callback', callback=self.callback_symbol_transform,
380 help=(
381 'transform symbol names from P to S, where P and S '
382 'use Python regexp and reference syntax '
383 'respectively. P must match the whole symbol name'
385 man_help=(
386 'Transform RCS/CVS symbol names before entering them into the '
387 'output history. \\fIpattern\\fR is a Python regexp pattern that '
388 'is matches against the entire symbol name; \\fIreplacement\\fR '
389 'is a replacement using Python\'s regexp reference syntax. '
390 'You may specify any number of these options; they will be '
391 'applied in the order given on the command line.'
393 metavar='P:S',
395 self.parser.set_default('symbol_strategy_rules', [])
396 group.add_option(IncompatibleOption(
397 '--symbol-hints', type='string',
398 action='callback', callback=self.callback_symbol_hints,
399 help='read symbol conversion hints from PATH',
400 man_help=(
401 'Read symbol conversion hints from \\fIpath\\fR. The format of '
402 '\\fIpath\\fR is the same as the format output by '
403 '\\fB--write-symbol-info\\fR, namely a text file with four '
404 'whitespace-separated columns: \\fIproject-id\\fR, '
405 '\\fIsymbol\\fR, \\fIconversion\\fR, and '
406 '\\fIparent-lod-name\\fR. \\fIproject-id\\fR is the numerical '
407 'ID of the project to which the symbol belongs, counting from '
408 '0. \\fIproject-id\\fR can be set to \'.\' if '
409 'project-specificity is not needed. \\fIsymbol-name\\fR is the '
410 'name of the symbol being specified. \\fIconversion\\fR '
411 'specifies how the symbol should be converted, and can be one '
412 'of the values \'branch\', \'tag\', or \'exclude\'. If '
413 '\\fIconversion\\fR is \'.\', then this rule does not affect '
414 'how the symbol is converted. \\fIparent-lod-name\\fR is the '
415 'name of the symbol from which this symbol should sprout, or '
416 '\'.trunk.\' if the symbol should sprout from trunk. If '
417 '\\fIparent-lod-name\\fR is omitted or \'.\', then this rule '
418 'does not affect the preferred parent of this symbol. The file '
419 'may contain blank lines or comment lines (lines whose first '
420 'non-whitespace character is \'#\').'
422 metavar='PATH',
424 self.parser.set_default('symbol_default', 'heuristic')
425 group.add_option(IncompatibleOption(
426 '--symbol-default', type='choice',
427 choices=['heuristic', 'strict', 'branch', 'tag', 'exclude'],
428 action='store',
429 help=(
430 'specify how ambiguous symbols are converted. '
431 'OPT is "heuristic" (default), "strict", "branch", '
432 '"tag" or "exclude"'
434 man_help=(
435 'Specify how to convert ambiguous symbols (those that appear in '
436 'the CVS archive as both branches and tags). \\fIopt\\fR must '
437 'be \'heuristic\' (decide how to treat each ambiguous symbol '
438 'based on whether it was used more often as a branch/tag in '
439 'CVS), \'strict\' (no default; every ambiguous symbol has to be '
440 'resolved manually using \\fB--force-branch\\fR, '
441 '\\fB--force-tag\\fR, or \\fB--exclude\\fR), \'branch\' (treat '
442 'every ambiguous symbol as a branch), \'tag\' (treat every '
443 'ambiguous symbol as a tag), or \'exclude\' (do not convert '
444 'ambiguous symbols). The default is \'heuristic\'.'
446 metavar='OPT',
448 group.add_option(IncompatibleOption(
449 '--force-branch', type='string',
450 action='callback', callback=self.callback_force_branch,
451 help='force symbols matching REGEXP to be branches',
452 man_help=(
453 'Force symbols whose names match \\fIregexp\\fR to be branches. '
454 '\\fIregexp\\fR must match the whole symbol name.'
456 metavar='REGEXP',
458 group.add_option(IncompatibleOption(
459 '--force-tag', type='string',
460 action='callback', callback=self.callback_force_tag,
461 help='force symbols matching REGEXP to be tags',
462 man_help=(
463 'Force symbols whose names match \\fIregexp\\fR to be tags. '
464 '\\fIregexp\\fR must match the whole symbol name.'
466 metavar='REGEXP',
468 group.add_option(IncompatibleOption(
469 '--exclude', type='string',
470 action='callback', callback=self.callback_exclude,
471 help='exclude branches and tags matching REGEXP',
472 man_help=(
473 'Exclude branches and tags whose names match \\fIregexp\\fR '
474 'from the conversion. \\fIregexp\\fR must match the whole '
475 'symbol name.'
477 metavar='REGEXP',
479 self.parser.set_default('keep_trivial_imports', False)
480 group.add_option(IncompatibleOption(
481 '--keep-trivial-imports',
482 action='store_true',
483 help=(
484 'do not exclude branches that were only used for '
485 'a single import (usually these are unneeded)'
487 man_help=(
488 'Do not exclude branches that were only used for a single '
489 'import. (By default such branches are excluded because they '
490 'are usually created by the inappropriate use of \\fBcvs '
491 'import\\fR.)'
495 return group
497 def _get_subversion_properties_options_group(self):
498 group = OptionGroup(self.parser, 'Subversion properties')
500 if self.DEFAULT_USERNAME is None:
501 default = 'The default is to use no author at all for such commits.'
502 else:
503 default = 'Default: "%s".' % (self.DEFAULT_USERNAME,)
505 group.add_option(ContextOption(
506 '--username', type='string', default=self.DEFAULT_USERNAME,
507 action='store',
508 help='username for synthesized commits. ' + default,
509 man_help=(
510 'Set the default username to \\fIname\\fR when this program needs '
511 'to generate a commit for which CVS does not record the '
512 'original username. This happens when a branch or tag is '
513 'created. '
514 + default
516 metavar='NAME',
518 self.parser.set_default('auto_props_files', [])
519 group.add_option(IncompatibleOption(
520 '--auto-props', type='string',
521 action='append', dest='auto_props_files',
522 help=(
523 'set file properties from the auto-props section '
524 'of a file in svn config format'
526 man_help=(
527 'Specify a file in the format of Subversion\'s config file, '
528 'whose [auto-props] section can be used to set arbitrary '
529 'properties on files in the Subversion repository based on '
530 'their filenames. (The [auto-props] section header must be '
531 'present; other sections of the config file, including the '
532 'enable-auto-props setting, are ignored.) Filenames are matched '
533 'to the filename patterns case-insensitively.'
536 metavar='FILE',
538 self.parser.set_default('mime_types_files', [])
539 group.add_option(IncompatibleOption(
540 '--mime-types', type='string',
541 action='append', dest='mime_types_files',
542 help=(
543 'specify an apache-style mime.types file for setting '
544 'svn:mime-type'
546 man_help=(
547 'Specify an apache-style mime.types \\fIfile\\fR for setting '
548 'svn:mime-type.'
550 metavar='FILE',
552 self.parser.set_default('eol_from_mime_type', False)
553 group.add_option(IncompatibleOption(
554 '--eol-from-mime-type',
555 action='store_true',
556 help='set svn:eol-style from mime type if known',
557 man_help=(
558 'For files that don\'t have the kb expansion mode but have a '
559 'known mime type, set the eol-style based on the mime type. '
560 'For such files, set svn:eol-style to "native" if the mime type '
561 'begins with "text/", and leave it unset (i.e., no EOL '
562 'translation) otherwise. Files with unknown mime types are '
563 'not affected by this option. This option has no effect '
564 'unless the \\fB--mime-types\\fR option is also specified.'
567 self.parser.set_default('default_eol', 'binary')
568 group.add_option(IncompatibleOption(
569 '--default-eol', type='choice',
570 choices=['binary', 'native', 'CRLF', 'LF', 'CR'],
571 action='store',
572 help=(
573 'default svn:eol-style for non-binary files with '
574 'undetermined mime types. STYLE is "binary" '
575 '(default), "native", "CRLF", "LF", or "CR"'
577 man_help=(
578 'Set svn:eol-style to \\fIstyle\\fR for files that don\'t have '
579 'the CVS \'kb\' expansion mode and whose end-of-line '
580 'translation mode hasn\'t been determined by one of the other '
581 'options. \\fIstyle\\fR must be \'binary\' (default), '
582 '\'native\', \'CRLF\', \'LF\', or \'CR\'.'
584 metavar='STYLE',
586 self.parser.set_default('keywords_off', False)
587 group.add_option(IncompatibleOption(
588 '--keywords-off',
589 action='store_true',
590 help=(
591 'don\'t set svn:keywords on any files (by default, '
592 'cvs2svn sets svn:keywords on non-binary files to "%s")'
593 % (config.SVN_KEYWORDS_VALUE,)
595 man_help=(
596 'By default, cvs2svn sets svn:keywords on CVS files to "author '
597 'id date" if the mode of the RCS file in question is either kv, '
598 'kvl or unset. If you use the --keywords-off switch, cvs2svn '
599 'will not set svn:keywords for any file. While this will not '
600 'touch the keywords in the contents of your files, Subversion '
601 'will not expand them.'
604 group.add_option(ContextOption(
605 '--keep-cvsignore',
606 action='store_true',
607 help=(
608 'keep .cvsignore files (in addition to creating '
609 'the analogous svn:ignore properties)'
611 man_help=(
612 'Include \\fI.cvsignore\\fR files in the output. (Normally '
613 'they are unneeded because cvs2svn sets the corresponding '
614 '\\fIsvn:ignore\\fR properties.)'
617 group.add_option(IncompatibleOption(
618 '--cvs-revnums',
619 action='callback', callback=self.callback_cvs_revnums,
620 help='record CVS revision numbers as file properties',
621 man_help=(
622 'Record CVS revision numbers as file properties in the '
623 'Subversion repository. (Note that unless it is removed '
624 'explicitly, the last CVS revision number will remain '
625 'associated with the file even after the file is changed '
626 'within Subversion.)'
630 # Deprecated options:
631 group.add_option(IncompatibleOption(
632 '--no-default-eol',
633 action='store_const', dest='default_eol', const=None,
634 help=optparse.SUPPRESS_HELP,
635 man_help=optparse.SUPPRESS_HELP,
637 self.parser.set_default('auto_props_ignore_case', True)
638 # True is the default now, so this option has no effect:
639 group.add_option(IncompatibleOption(
640 '--auto-props-ignore-case',
641 action='store_true',
642 help=optparse.SUPPRESS_HELP,
643 man_help=optparse.SUPPRESS_HELP,
646 return group
648 def _get_extraction_options_group(self):
649 group = OptionGroup(self.parser, 'Extraction options')
651 return group
653 def _add_use_internal_co_option(self, group):
654 self.parser.set_default('use_internal_co', False)
655 group.add_option(IncompatibleOption(
656 '--use-internal-co',
657 action='store_true',
658 help=(
659 'use internal code to extract revision contents '
660 '(fastest but disk space intensive) (default)'
662 man_help=(
663 'Use internal code to extract revision contents. This '
664 'is up to 50% faster than using \\fB--use-rcs\\fR, but needs '
665 'a lot of disk space: roughly the size of your CVS repository '
666 'plus the peak size of a complete checkout of the repository '
667 'with all branches that existed and still had commits pending '
668 'at a given time. This option is the default.'
672 def _add_use_cvs_option(self, group):
673 self.parser.set_default('use_cvs', False)
674 group.add_option(IncompatibleOption(
675 '--use-cvs',
676 action='store_true',
677 help=(
678 'use CVS to extract revision contents (slower than '
679 '--use-internal-co or --use-rcs)'
681 man_help=(
682 'Use CVS to extract revision contents. This option is slower '
683 'than \\fB--use-internal-co\\fR or \\fB--use-rcs\\fR.'
687 def _add_use_rcs_option(self, group):
688 self.parser.set_default('use_rcs', False)
689 group.add_option(IncompatibleOption(
690 '--use-rcs',
691 action='store_true',
692 help=(
693 'use RCS to extract revision contents (faster than '
694 '--use-cvs but fails in some cases)'
696 man_help=(
697 'Use RCS \'co\' to extract revision contents. This option is '
698 'faster than \\fB--use-cvs\\fR but fails in some cases.'
702 def _get_environment_options_group(self):
703 group = OptionGroup(self.parser, 'Environment options')
704 group.add_option(ContextOption(
705 '--tmpdir', type='string',
706 action='store',
707 help=(
708 'directory to use for temporary data files '
709 '(default is to create a temporary subdirectory under %r)'
710 ) % (tempfile.gettempdir(),),
711 man_help=(
712 'Set the \\fIpath\\fR to use for temporary data. The default '
713 'is to create a temporary subdirectory under \\fI%s\\fR.'
714 ) % (tempfile.gettempdir(),),
715 metavar='PATH',
717 self.parser.set_default('co_executable', config.CO_EXECUTABLE)
718 group.add_option(IncompatibleOption(
719 '--co', type='string',
720 action='store', dest='co_executable',
721 help='path to the "co" program (required if --use-rcs)',
722 man_help=(
723 'Path to the \\fIco\\fR program. (\\fIco\\fR is needed if the '
724 '\\fB--use-rcs\\fR option is used.)'
726 metavar='PATH',
728 self.parser.set_default('cvs_executable', config.CVS_EXECUTABLE)
729 group.add_option(IncompatibleOption(
730 '--cvs', type='string',
731 action='store', dest='cvs_executable',
732 help='path to the "cvs" program (required if --use-cvs)',
733 man_help=(
734 'Path to the \\fIcvs\\fR program. (\\fIcvs\\fR is needed if the '
735 '\\fB--use-cvs\\fR option is used.)'
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 prof = 'cProfile'
844 try:
845 import cProfile
846 except ImportError:
847 prof = 'hotshot'
848 group.add_option(ManOption(
849 '--profile',
850 action='callback', callback=self.callback_profile,
851 help='profile with \'' + prof + '\' (into file cvs2svn.' + prof + ')',
852 man_help=(
853 'Profile with \'' + prof + '\' (into file \\fIcvs2svn.' + prof + '\\fR).'
857 return group
859 def callback_options(self, option, opt_str, value, parser):
860 parser.values.options_file_found = True
861 self.process_options_file(value)
863 def callback_encoding(self, option, opt_str, value, parser):
864 ctx = Ctx()
866 try:
867 ctx.cvs_author_decoder.add_encoding(value)
868 ctx.cvs_log_decoder.add_encoding(value)
869 ctx.cvs_filename_decoder.add_encoding(value)
870 except LookupError, e:
871 raise FatalError(str(e))
873 def callback_fallback_encoding(self, option, opt_str, value, parser):
874 ctx = Ctx()
876 try:
877 ctx.cvs_author_decoder.set_fallback_encoding(value)
878 ctx.cvs_log_decoder.set_fallback_encoding(value)
879 # Don't use fallback_encoding for filenames.
880 except LookupError, e:
881 raise FatalError(str(e))
883 def callback_help_passes(self, option, opt_str, value, parser):
884 self.pass_manager.help_passes()
885 sys.exit(0)
887 def _choose_build_date(self):
888 """Choose the data to embed in the man pages.
890 If environment variable SOURCE_DATE_EPOCH is set, use that.
891 Otherwise, use the current time."""
893 t = os.environ.get('SOURCE_DATE_EPOCH')
894 if t:
895 return datetime.datetime.utcfromtimestamp(int(t)).date()
896 else:
897 return datetime.date.today()
899 def callback_manpage(self, option, opt_str, value, parser):
900 f = codecs.getwriter('utf_8')(sys.stdout)
901 writer = ManWriter(parser,
902 section='1',
903 date=self._choose_build_date(),
904 source='Version %s' % (VERSION,),
905 manual='User Commands',
906 short_desc=self.short_desc,
907 synopsis=self.synopsis,
908 long_desc=self.long_desc,
909 files=self.files,
910 authors=self.authors,
911 see_also=self.see_also)
912 writer.write_manpage(f)
913 sys.exit(0)
915 def callback_version(self, option, opt_str, value, parser):
916 sys.stdout.write(
917 '%s version %s\n' % (self.progname, VERSION)
919 sys.exit(0)
921 def callback_verbose(self, option, opt_str, value, parser):
922 logger.increase_verbosity()
924 def callback_quiet(self, option, opt_str, value, parser):
925 logger.decrease_verbosity()
927 def callback_passes(self, option, opt_str, value, parser):
928 if value.find(':') >= 0:
929 start_pass, end_pass = value.split(':')
930 self.start_pass = self.pass_manager.get_pass_number(start_pass, 1)
931 self.end_pass = self.pass_manager.get_pass_number(
932 end_pass, self.pass_manager.num_passes
934 else:
935 self.end_pass = \
936 self.start_pass = \
937 self.pass_manager.get_pass_number(value)
939 def callback_profile(self, option, opt_str, value, parser):
940 self.profiling = True
942 def callback_symbol_hints(self, option, opt_str, value, parser):
943 parser.values.symbol_strategy_rules.append(SymbolHintsFileRule(value))
945 def callback_force_branch(self, option, opt_str, value, parser):
946 parser.values.symbol_strategy_rules.append(
947 ForceBranchRegexpStrategyRule(value)
950 def callback_force_tag(self, option, opt_str, value, parser):
951 parser.values.symbol_strategy_rules.append(
952 ForceTagRegexpStrategyRule(value)
955 def callback_exclude(self, option, opt_str, value, parser):
956 parser.values.symbol_strategy_rules.append(
957 ExcludeRegexpStrategyRule(value)
960 def callback_cvs_revnums(self, option, opt_str, value, parser):
961 Ctx().revision_property_setters.append(CVSRevisionNumberSetter())
963 def callback_symbol_transform(self, option, opt_str, value, parser):
964 [pattern, replacement] = value.split(":")
965 try:
966 parser.values.symbol_transforms.append(
967 RegexpSymbolTransform(pattern, replacement)
969 except re.error:
970 raise FatalError("'%s' is not a valid regexp." % (pattern,))
972 # Common to SVNRunOptions, HgRunOptions (GitRunOptions and
973 # BzrRunOptions do not support --use-internal-co, so cannot use this).
974 def process_all_extraction_options(self):
975 ctx = Ctx()
976 options = self.options
978 not_both(options.use_rcs, '--use-rcs',
979 options.use_cvs, '--use-cvs')
981 not_both(options.use_rcs, '--use-rcs',
982 options.use_internal_co, '--use-internal-co')
984 not_both(options.use_cvs, '--use-cvs',
985 options.use_internal_co, '--use-internal-co')
987 if options.use_rcs:
988 ctx.revision_collector = NullRevisionCollector()
989 ctx.revision_reader = RCSRevisionReader(options.co_executable)
990 elif options.use_cvs:
991 ctx.revision_collector = NullRevisionCollector()
992 ctx.revision_reader = CVSRevisionReader(options.cvs_executable)
993 else:
994 # --use-internal-co is the default:
995 ctx.revision_collector = InternalRevisionCollector(compress=True)
996 ctx.revision_reader = InternalRevisionReader(compress=True)
998 def process_symbol_strategy_options(self):
999 """Process symbol strategy-related options."""
1001 ctx = Ctx()
1002 options = self.options
1004 # Add the standard symbol name cleanup rules:
1005 self.options.symbol_transforms.extend([
1006 ReplaceSubstringsSymbolTransform('\\','/'),
1007 # Remove leading, trailing, and repeated slashes:
1008 NormalizePathsSymbolTransform(),
1011 if ctx.trunk_only:
1012 if options.symbol_strategy_rules or options.keep_trivial_imports:
1013 raise SymbolOptionsWithTrunkOnlyException()
1015 else:
1016 if not options.keep_trivial_imports:
1017 options.symbol_strategy_rules.append(ExcludeTrivialImportBranchRule())
1019 options.symbol_strategy_rules.append(UnambiguousUsageRule())
1020 if options.symbol_default == 'strict':
1021 pass
1022 elif options.symbol_default == 'branch':
1023 options.symbol_strategy_rules.append(AllBranchRule())
1024 elif options.symbol_default == 'tag':
1025 options.symbol_strategy_rules.append(AllTagRule())
1026 elif options.symbol_default == 'heuristic':
1027 options.symbol_strategy_rules.append(BranchIfCommitsRule())
1028 options.symbol_strategy_rules.append(HeuristicStrategyRule())
1029 elif options.symbol_default == 'exclude':
1030 options.symbol_strategy_rules.append(AllExcludedRule())
1031 else:
1032 assert False
1034 # Now add a rule whose job it is to pick the preferred parents of
1035 # branches and tags:
1036 options.symbol_strategy_rules.append(HeuristicPreferredParentRule())
1038 def process_property_setter_options(self):
1039 """Process the options that set SVN properties."""
1041 ctx = Ctx()
1042 options = self.options
1044 for value in options.auto_props_files:
1045 ctx.file_property_setters.append(
1046 AutoPropsPropertySetter(value, options.auto_props_ignore_case)
1049 for value in options.mime_types_files:
1050 ctx.file_property_setters.append(MimeMapper(value))
1052 ctx.file_property_setters.append(CVSBinaryFileEOLStyleSetter())
1054 ctx.file_property_setters.append(CVSBinaryFileDefaultMimeTypeSetter())
1056 if options.eol_from_mime_type:
1057 ctx.file_property_setters.append(EOLStyleFromMimeTypeSetter())
1059 ctx.file_property_setters.append(
1060 DefaultEOLStyleSetter(options.default_eol)
1063 ctx.file_property_setters.append(SVNBinaryFileKeywordsPropertySetter())
1065 if not options.keywords_off:
1066 ctx.file_property_setters.append(
1067 KeywordsPropertySetter(config.SVN_KEYWORDS_VALUE)
1070 ctx.file_property_setters.append(ExecutablePropertySetter())
1072 ctx.file_property_setters.append(DescriptionPropertySetter())
1074 def process_options(self):
1075 """Do the main configuration based on command-line options.
1077 This method is only called if the --options option was not
1078 specified."""
1080 raise NotImplementedError()
1082 def check_options(self):
1083 """Check the the run options are OK.
1085 This should only be called after all options have been processed."""
1087 # Convenience var, so we don't have to keep instantiating this Borg.
1088 ctx = Ctx()
1090 if not self.start_pass <= self.end_pass:
1091 raise InvalidPassError(
1092 'Ending pass must not come before starting pass.')
1094 if not ctx.dry_run and ctx.output_option is None:
1095 raise FatalError('No output option specified.')
1097 if ctx.output_option is not None:
1098 ctx.output_option.check()
1100 if not self.projects:
1101 raise FatalError('No project specified.')
1103 def verify_option_compatibility(self):
1104 """Verify that no options incompatible with --options were used.
1106 The --options option was specified. Verify that no incompatible
1107 options or arguments were specified."""
1109 if self.options.options_incompatible_options or self.args:
1110 if self.options.options_incompatible_options:
1111 oio = self.options.options_incompatible_options
1112 logger.error(
1113 '%s: The following options cannot be used in combination with '
1114 'the --options\n'
1115 'option:\n'
1116 ' %s\n'
1117 % (error_prefix, '\n '.join(oio))
1119 if self.args:
1120 logger.error(
1121 '%s: No cvs-repos-path arguments are allowed with the --options '
1122 'option.\n'
1123 % (error_prefix,)
1125 sys.exit(1)
1127 def process_options_file(self, options_filename):
1128 """Read options from the file named OPTIONS_FILENAME.
1130 Store the run options to SELF."""
1132 g = {
1133 'ctx' : Ctx(),
1134 'run_options' : self,
1136 execfile(options_filename, g)
1138 def usage(self):
1139 self.parser.print_help()