diff options
Diffstat (limited to 'cvs2svn_lib/svn_run_options.py')
-rw-r--r-- | cvs2svn_lib/svn_run_options.py | 543 |
1 files changed, 543 insertions, 0 deletions
diff --git a/cvs2svn_lib/svn_run_options.py b/cvs2svn_lib/svn_run_options.py new file mode 100644 index 0000000..e757730 --- /dev/null +++ b/cvs2svn_lib/svn_run_options.py @@ -0,0 +1,543 @@ +# (Be in -*- python -*- mode.) +# +# ==================================================================== +# Copyright (c) 2000-2009 CollabNet. All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://subversion.tigris.org/license-1.html. +# If newer versions of this license are posted there, you may use a +# newer version instead, at your option. +# +# This software consists of voluntary contributions made by many +# individuals. For exact contribution history, see the revision +# history and logs, available at http://cvs2svn.tigris.org/. +# ==================================================================== + +"""This module manages cvs2svn run options.""" + + +import sys +import optparse +import datetime +import codecs + +from cvs2svn_lib.version import VERSION +from cvs2svn_lib import config +from cvs2svn_lib.common import warning_prefix +from cvs2svn_lib.common import error_prefix +from cvs2svn_lib.common import FatalError +from cvs2svn_lib.common import normalize_svn_path +from cvs2svn_lib.log import Log +from cvs2svn_lib.context import Ctx +from cvs2svn_lib.run_options import not_both +from cvs2svn_lib.run_options import RunOptions +from cvs2svn_lib.run_options import ContextOption +from cvs2svn_lib.run_options import IncompatibleOption +from cvs2svn_lib.run_options import authors +from cvs2svn_lib.man_writer import ManWriter +from cvs2svn_lib.project import Project +from cvs2svn_lib.svn_output_option import DumpfileOutputOption +from cvs2svn_lib.svn_output_option import ExistingRepositoryOutputOption +from cvs2svn_lib.svn_output_option import NewRepositoryOutputOption +from cvs2svn_lib.revision_manager import NullRevisionRecorder +from cvs2svn_lib.revision_manager import NullRevisionExcluder +from cvs2svn_lib.rcs_revision_manager import RCSRevisionReader +from cvs2svn_lib.cvs_revision_manager import CVSRevisionReader +from cvs2svn_lib.checkout_internal import InternalRevisionRecorder +from cvs2svn_lib.checkout_internal import InternalRevisionExcluder +from cvs2svn_lib.checkout_internal import InternalRevisionReader +from cvs2svn_lib.symbol_strategy import TrunkPathRule +from cvs2svn_lib.symbol_strategy import BranchesPathRule +from cvs2svn_lib.symbol_strategy import TagsPathRule + + +short_desc = 'convert a cvs repository into a subversion repository' + +synopsis = """\ +.B cvs2svn +[\\fIOPTION\\fR]... \\fIOUTPUT-OPTION CVS-REPOS-PATH\\fR +.br +.B cvs2svn +[\\fIOPTION\\fR]... \\fI--options=PATH\\fR +""" + +long_desc = """\ +Create a new Subversion repository based on the version history stored in a +CVS repository. Each CVS commit will be mirrored in the Subversion +repository, including such information as date of commit and id of the +committer. +.P +\\fICVS-REPOS-PATH\\fR is the filesystem path of the part of the CVS +repository that you want to convert. It is not possible to convert a +CVS repository to which you only have remote access; see the FAQ for +more information. This path doesn't have to be the top level +directory of a CVS repository; it can point at a project within a +repository, in which case only that project will be converted. This +path or one of its parent directories has to contain a subdirectory +called CVSROOT (though the CVSROOT directory can be empty). +.P +Multiple CVS repositories can be converted into a single Subversion +repository in a single run of cvs2svn, but only by using an +\\fB--options\\fR file. +""" + +files = """\ +A directory called \\fIcvs2svn-tmp\\fR (or the directory specified by +\\fB--tmpdir\\fR) is used as scratch space for temporary data files. +""" + +see_also = [ + ('cvs', '1'), + ('svn', '1'), + ('svnadmin', '1'), + ] + + +class SVNRunOptions(RunOptions): + def _get_output_options_group(self): + group = RunOptions._get_output_options_group(self) + + group.add_option(IncompatibleOption( + '--svnrepos', '-s', type='string', + action='store', + help='path where SVN repos should be created', + man_help=( + 'Write the output of the conversion into a Subversion repository ' + 'located at \\fIpath\\fR. This option causes a new Subversion ' + 'repository to be created at \\fIpath\\fR unless the ' + '\\fB--existing-svnrepos\\fR option is also used.' + ), + metavar='PATH', + )) + self.parser.set_default('existing_svnrepos', False) + group.add_option(IncompatibleOption( + '--existing-svnrepos', + action='store_true', + help='load into existing SVN repository (for use with --svnrepos)', + man_help=( + 'Load the converted CVS repository into an existing Subversion ' + 'repository, instead of creating a new repository. (This option ' + 'should be used in combination with ' + '\\fB-s\\fR/\\fB--svnrepos\\fR.) The repository must either be ' + 'empty or contain no paths that overlap with those that will ' + 'result from the conversion. Please note that you need write ' + 'permission for the repository files.' + ), + )) + group.add_option(IncompatibleOption( + '--fs-type', type='string', + action='store', + help=( + 'pass --fs-type=TYPE to "svnadmin create" (for use with ' + '--svnrepos)' + ), + man_help=( + 'Pass \\fI--fs-type\\fR=\\fItype\\fR to "svnadmin create" when ' + 'creating a new repository.' + ), + metavar='TYPE', + )) + self.parser.set_default('bdb_txn_nosync', False) + group.add_option(IncompatibleOption( + '--bdb-txn-nosync', + action='store_true', + help=( + 'pass --bdb-txn-nosync to "svnadmin create" (for use with ' + '--svnrepos)' + ), + man_help=( + 'Pass \\fI--bdb-txn-nosync\\fR to "svnadmin create" when ' + 'creating a new BDB-style Subversion repository.' + ), + )) + self.parser.set_default('create_options', []) + group.add_option(IncompatibleOption( + '--create-option', type='string', + action='append', dest='create_options', + help='pass OPT to "svnadmin create" (for use with --svnrepos)', + man_help=( + 'Pass \\fIopt\\fR to "svnadmin create" when creating a new ' + 'Subversion repository (can be specified multiple times to ' + 'pass multiple options).' + ), + metavar='OPT', + )) + group.add_option(IncompatibleOption( + '--dumpfile', type='string', + action='store', + help='just produce a dumpfile; don\'t commit to a repos', + man_help=( + 'Just produce a dumpfile; don\'t commit to an SVN repository. ' + 'Write the dumpfile to \\fIpath\\fR.' + ), + metavar='PATH', + )) + + group.add_option(ContextOption( + '--dry-run', + action='store_true', + help=( + 'do not create a repository or a dumpfile; just print what ' + 'would happen.' + ), + man_help=( + 'Do not create a repository or a dumpfile; just print the ' + 'details of what cvs2svn would do if it were really converting ' + 'your repository.' + ), + )) + + # Deprecated options: + self.parser.set_default('dump_only', False) + group.add_option(IncompatibleOption( + '--dump-only', + action='callback', callback=self.callback_dump_only, + help=optparse.SUPPRESS_HELP, + man_help=optparse.SUPPRESS_HELP, + )) + group.add_option(IncompatibleOption( + '--create', + action='callback', callback=self.callback_create, + help=optparse.SUPPRESS_HELP, + man_help=optparse.SUPPRESS_HELP, + )) + + return group + + def _get_conversion_options_group(self): + group = RunOptions._get_conversion_options_group(self) + + self.parser.set_default('trunk_base', config.DEFAULT_TRUNK_BASE) + group.add_option(IncompatibleOption( + '--trunk', type='string', + action='store', dest='trunk_base', + help=( + 'path for trunk (default: %s)' + % (config.DEFAULT_TRUNK_BASE,) + ), + man_help=( + 'Set the top-level path to use for trunk in the Subversion ' + 'repository. The default is \\fI%s\\fR.' + % (config.DEFAULT_TRUNK_BASE,) + ), + metavar='PATH', + )) + self.parser.set_default('branches_base', config.DEFAULT_BRANCHES_BASE) + group.add_option(IncompatibleOption( + '--branches', type='string', + action='store', dest='branches_base', + help=( + 'path for branches (default: %s)' + % (config.DEFAULT_BRANCHES_BASE,) + ), + man_help=( + 'Set the top-level path to use for branches in the Subversion ' + 'repository. The default is \\fI%s\\fR.' + % (config.DEFAULT_BRANCHES_BASE,) + ), + metavar='PATH', + )) + self.parser.set_default('tags_base', config.DEFAULT_TAGS_BASE) + group.add_option(IncompatibleOption( + '--tags', type='string', + action='store', dest='tags_base', + help=( + 'path for tags (default: %s)' + % (config.DEFAULT_TAGS_BASE,) + ), + man_help=( + 'Set the top-level path to use for tags in the Subversion ' + 'repository. The default is \\fI%s\\fR.' + % (config.DEFAULT_TAGS_BASE,) + ), + metavar='PATH', + )) + group.add_option(ContextOption( + '--no-prune', + action='store_false', dest='prune', + help='don\'t prune empty directories', + man_help=( + 'When all files are deleted from a directory in the Subversion ' + 'repository, don\'t delete the empty directory (the default is ' + 'to delete any empty directories).' + ), + )) + group.add_option(ContextOption( + '--no-cross-branch-commits', + action='store_false', dest='cross_branch_commits', + help='prevent the creation of cross-branch commits', + man_help=( + 'Prevent the creation of commits that affect files on multiple ' + 'branches at once.' + ), + )) + + return group + + def _get_extraction_options_group(self): + group = RunOptions._get_extraction_options_group(self) + + self.parser.set_default('use_internal_co', False) + group.add_option(IncompatibleOption( + '--use-internal-co', + action='store_true', + help=( + 'use internal code to extract revision contents ' + '(fastest but disk space intensive) (default)' + ), + man_help=( + 'Use internal code to extract revision contents. This ' + 'is up to 50% faster than using \\fB--use-rcs\\fR, but needs ' + 'a lot of disk space: roughly the size of your CVS repository ' + 'plus the peak size of a complete checkout of the repository ' + 'with all branches that existed and still had commits pending ' + 'at a given time. This option is the default.' + ), + )) + self.parser.set_default('use_cvs', False) + group.add_option(IncompatibleOption( + '--use-cvs', + action='store_true', + help=( + 'use CVS to extract revision contents (slower than ' + '--use-internal-co or --use-rcs)' + ), + man_help=( + 'Use CVS to extract revision contents. This option is slower ' + 'than \\fB--use-internal-co\\fR or \\fB--use-rcs\\fR.' + ), + )) + self.parser.set_default('use_rcs', False) + group.add_option(IncompatibleOption( + '--use-rcs', + action='store_true', + help=( + 'use RCS to extract revision contents (faster than ' + '--use-cvs but fails in some cases)' + ), + man_help=( + 'Use RCS \'co\' to extract revision contents. This option is ' + 'faster than \\fB--use-cvs\\fR but fails in some cases.' + ), + )) + + return group + + def _get_environment_options_group(self): + group = RunOptions._get_environment_options_group(self) + + group.add_option(ContextOption( + '--svnadmin', type='string', + action='store', dest='svnadmin_executable', + help='path to the "svnadmin" program', + man_help=( + 'Path to the \\fIsvnadmin\\fR program. (\\fIsvnadmin\\fR is ' + 'needed when the \\fB-s\\fR/\\fB--svnrepos\\fR output option is ' + 'used.)' + ), + metavar='PATH', + )) + + return group + + def callback_dump_only(self, option, opt_str, value, parser): + parser.values.dump_only = True + Log().error( + warning_prefix + + ': The --dump-only option is deprecated (it is implied ' + 'by --dumpfile).\n' + ) + + def callback_create(self, option, opt_str, value, parser): + Log().error( + warning_prefix + + ': The behaviour produced by the --create option is now the ' + 'default;\n' + 'passing the option is deprecated.\n' + ) + + def callback_manpage(self, option, opt_str, value, parser): + f = codecs.getwriter('utf_8')(sys.stdout) + ManWriter( + parser, + section='1', + date=datetime.date.today(), + source='Version %s' % (VERSION,), + manual='User Commands', + short_desc=short_desc, + synopsis=synopsis, + long_desc=long_desc, + files=files, + authors=authors, + see_also=see_also, + ).write_manpage(f) + sys.exit(0) + + def process_extraction_options(self): + """Process options related to extracting data from the CVS repository.""" + + ctx = Ctx() + options = self.options + + not_both(options.use_rcs, '--use-rcs', + options.use_cvs, '--use-cvs') + + not_both(options.use_rcs, '--use-rcs', + options.use_internal_co, '--use-internal-co') + + not_both(options.use_cvs, '--use-cvs', + options.use_internal_co, '--use-internal-co') + + if options.use_rcs: + ctx.revision_recorder = NullRevisionRecorder() + ctx.revision_excluder = NullRevisionExcluder() + ctx.revision_reader = RCSRevisionReader(options.co_executable) + elif options.use_cvs: + ctx.revision_recorder = NullRevisionRecorder() + ctx.revision_excluder = NullRevisionExcluder() + ctx.revision_reader = CVSRevisionReader(options.cvs_executable) + else: + # --use-internal-co is the default: + ctx.revision_recorder = InternalRevisionRecorder(compress=True) + ctx.revision_excluder = InternalRevisionExcluder() + ctx.revision_reader = InternalRevisionReader(compress=True) + + def process_output_options(self): + """Process the options related to SVN output.""" + + ctx = Ctx() + options = self.options + + if options.dump_only and not options.dumpfile: + raise FatalError("'--dump-only' requires '--dumpfile' to be specified.") + + if not options.svnrepos and not options.dumpfile and not ctx.dry_run: + raise FatalError("must pass one of '-s' or '--dumpfile'.") + + not_both(options.svnrepos, '-s', + options.dumpfile, '--dumpfile') + + not_both(options.dumpfile, '--dumpfile', + options.existing_svnrepos, '--existing-svnrepos') + + not_both(options.bdb_txn_nosync, '--bdb-txn-nosync', + options.existing_svnrepos, '--existing-svnrepos') + + not_both(options.dumpfile, '--dumpfile', + options.bdb_txn_nosync, '--bdb-txn-nosync') + + not_both(options.fs_type, '--fs-type', + options.existing_svnrepos, '--existing-svnrepos') + + if ( + options.fs_type + and options.fs_type != 'bdb' + and options.bdb_txn_nosync + ): + raise FatalError("cannot pass --bdb-txn-nosync with --fs-type=%s." + % options.fs_type) + + if options.svnrepos: + if options.existing_svnrepos: + ctx.output_option = ExistingRepositoryOutputOption(options.svnrepos) + else: + ctx.output_option = NewRepositoryOutputOption( + options.svnrepos, + fs_type=options.fs_type, bdb_txn_nosync=options.bdb_txn_nosync, + create_options=options.create_options) + else: + ctx.output_option = DumpfileOutputOption(options.dumpfile) + + def add_project( + self, + project_cvs_repos_path, + trunk_path=None, branches_path=None, tags_path=None, + initial_directories=[], + symbol_transforms=None, + symbol_strategy_rules=[], + ): + """Add a project to be converted. + + Most arguments are passed straight through to the Project + constructor. SYMBOL_STRATEGY_RULES is an iterable of + SymbolStrategyRules that will be applied to symbols in this + project.""" + + if trunk_path is not None: + trunk_path = normalize_svn_path(trunk_path, allow_empty=True) + if branches_path is not None: + branches_path = normalize_svn_path(branches_path, allow_empty=False) + if tags_path is not None: + tags_path = normalize_svn_path(tags_path, allow_empty=False) + + initial_directories = [ + path + for path in [trunk_path, branches_path, tags_path] + if path + ] + [ + normalize_svn_path(path) + for path in initial_directories + ] + + symbol_strategy_rules = list(symbol_strategy_rules) + + # Add rules to set the SVN paths for LODs depending on whether + # they are the trunk, tags, or branches: + if trunk_path is not None: + symbol_strategy_rules.append(TrunkPathRule(trunk_path)) + if branches_path is not None: + symbol_strategy_rules.append(BranchesPathRule(branches_path)) + if tags_path is not None: + symbol_strategy_rules.append(TagsPathRule(tags_path)) + + id = len(self.projects) + project = Project( + id, + project_cvs_repos_path, + initial_directories=initial_directories, + symbol_transforms=symbol_transforms, + ) + + self.projects.append(project) + self.project_symbol_strategy_rules.append(symbol_strategy_rules) + + def clear_projects(self): + """Clear the list of projects to be converted. + + This method is for the convenience of options files, which may + want to import one another.""" + + del self.projects[:] + del self.project_symbol_strategy_rules[:] + + def process_options(self): + # Consistency check for options and arguments. + if len(self.args) == 0: + self.usage() + sys.exit(1) + + if len(self.args) > 1: + Log().error(error_prefix + ": must pass only one CVS repository.\n") + self.usage() + sys.exit(1) + + cvsroot = self.args[0] + + self.process_extraction_options() + self.process_output_options() + self.process_symbol_strategy_options() + self.process_property_setter_options() + + # Create the default project (using ctx.trunk, ctx.branches, and + # ctx.tags): + self.add_project( + cvsroot, + trunk_path=self.options.trunk_base, + branches_path=self.options.branches_base, + tags_path=self.options.tags_base, + symbol_transforms=self.options.symbol_transforms, + symbol_strategy_rules=self.options.symbol_strategy_rules, + ) + + |