# (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, )