diff options
author | Ian Clatworthy <ian.clatworthy@canonical.com> | 2009-08-11 23:27:57 +1000 |
---|---|---|
committer | Ian Clatworthy <ian.clatworthy@canonical.com> | 2009-08-11 23:27:57 +1000 |
commit | 19504519286f5c2a960863117fc916166e19fac3 (patch) | |
tree | 28162770468becf757a9939c4c09bafe2479f0da /exporters | |
parent | e038af0407d5b145fc2f8cde4e9e145aa53f9c3a (diff) | |
parent | d64c8c5d77ee83e145cf06a0085d715f6de243d9 (diff) | |
download | bzr-fastimport-19504519286f5c2a960863117fc916166e19fac3.tar.gz |
first batch of fast-export-from-xxx commands
Diffstat (limited to 'exporters')
-rw-r--r-- | exporters/__init__.py | 255 | ||||
-rwxr-xr-x | exporters/hg-fast-export.py | 12 |
2 files changed, 261 insertions, 6 deletions
diff --git a/exporters/__init__.py b/exporters/__init__.py new file mode 100644 index 0000000..68ac105 --- /dev/null +++ b/exporters/__init__.py @@ -0,0 +1,255 @@ +# Copyright (C) 2009 Canonical Ltd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +"""Simplified and unified access to the various xxx-fast-export tools.""" + + +import gzip, os, subprocess, sys + +from bzrlib import errors +from bzrlib.trace import note, warning + + +class MissingDependency(Exception): + + def __init__(self, tool, minimum_version, missing): + self.tool = tool + self.minimum_version = minimum_version + self.missing = missing + + def get_message(self): + return "%s missing. Please install %s %s or later and try again." % \ + (self.missing, self.tool, self.minimum_version) + + +class _Exporter(object): + + def check_install(self, tool_name, minimum_version, required_commands=None, + required_libraries=None): + """Check dependencies are correctly installed. + + :param tool_name: name of the tool + :param minimum_version: minimum version required + :param required_commands: list of commands that must be on the path + :param required_libraries: list of Python libraries that must be + available + :raises MissingDependency: if a required dependency is not found + """ + self.tool_name = tool_name + self.minimum_version = minimum_version + if required_commands: + for cmd in required_commands: + self._check_cmd_available(cmd) + if required_libraries: + for lib in required_libraries: + self._check_lib_available(lib) + + def _check_cmd_available(self, cmd): + try: + if isinstance(cmd, str): + args = [cmd] + else: + args = cmd + retcode = subprocess.call(args, stdout=subprocess.PIPE) + except OSError: + raise MissingDependency(self.tool_name, self.minimum_version, cmd) + + def _check_lib_available(self, lib): + try: + __import__(lib) + except ImportError: + raise MissingDependency(self.tool_name, self.minimum_version, lib) + + def generate(self, source, destination, verbose=False, custom=None): + """Generate a fast import stream. + + :param source: the source filename or URL + :param destination: filename or '-' for standard output + :param verbose: if True, output additional diagnostics + :param custom: a list of custom options to be added to the + command line of the underlying scripts used. If an option + and its argument are to be separated by a space, pass them + as consecutive items. + """ + raise NotImplementedError(self.generate) + + def get_output_info(self, dest): + """Get the output streams/filenames given a destination filename. + + :return: outf, basename, marks where + outf is a file-like object for storing the output, + basename is the name without the .fi and .gz prefixes + marks is the name of the marks file to use, if any + """ + if dest == '-': + return sys.stdout, None, None + else: + #if dest.endswith('.gz'): + # outf = gzip.open(dest, 'wb') + # base = dest[:-3] + #else: + outf = open(dest, 'w') + base = dest + if base.endswith(".fi"): + base = dest[:-3] + marks = "%s.marks" % (base,) + return outf, base, marks + + def execute(self, args, outf, cwd=None): + """Execute a command, capture the output and close files. + + :param args: list of arguments making up the command + :param outf: a file-like object for storing the output, + :param cwd: current working directory to use + :return: the return code + """ + if cwd is not None: + note("Executing %s in directory %s ..." % (" ".join(args), source)) + else: + note("Executing %s ..." % (" ".join(args),)) + try: + p = subprocess.Popen(args, stdout=outf, cwd=cwd) + p.wait() + finally: + if outf != sys.stdout: + outf.close() + return p.returncode + + def report_results(self, retcode, destination): + """Report whether the export succeeded or otherwise.""" + if retcode == 0: + note("Export to %s completed successfully." % (destination,)) + else: + warning("Export to %s exited with error code %d." + % (destination, retcode)) + + def execute_exporter_script(self, args, outf): + """Execute an exporter script, capturing the output. + + The script must be a Python script under the exporters directory. + + :param args: list of arguments making up the script, the first of + which is the script name relative to the exporters directory. + :param outf: a file-like object for storing the output, + :return: the return code + """ + # Note: currently assume Python is on the path. We could work around + # this later (for Windows users say) by packaging the scripts as Python + # modules and calling their internals directly. + exporters_dir = os.path.dirname(__file__) + script_abspath = os.path.join(exporters_dir, args[0]) + actual_args = ['python', script_abspath] + args[1:] + return self.execute(actual_args, outf) + + +class DarcsExporter(_Exporter): + + def __init__(self): + self.check_install('Darcs', '2.2', [('darcs', '--version')]) + + def generate(self, source, destination, verbose=False, custom=None): + """Generate a fast import stream. See _Exporter.generate() for details.""" + args = ["darcs/darcs-fast-export"] + outf, base, marks = self.get_output_info(destination) + if marks: + args.append('--export-marks=%s' % marks) + if custom: + args.extend(custom) + args.append(source) + retcode = self.execute_exporter_script(args, outf) + self.report_results(retcode, destination) + + +class MercurialExporter(_Exporter): + + def __init__(self): + self.check_install('Mercurial', '1.2', None, ['mercurial']) + + def generate(self, source, destination, verbose=False, custom=None): + """Generate a fast import stream. See _Exporter.generate() for details.""" + # XXX: Should we add --force here? + args = ["hg-fast-export.py", "-r", source, "-s"] + outf, base, marks = self.get_output_info(destination) + if base: + args.append('--marks=%s.marks' % (base,)) + args.append('--mapping=%s.mapping' % (base,)) + args.append('--heads=%s.heads' % (base,)) + args.append('--status=%s.status' % (base,)) + if custom: + args.extend(custom) + retcode = self.execute_exporter_script(args, outf) + self.report_results(retcode, destination) + + +class GitExporter(_Exporter): + + def __init__(self): + self.check_install('Git', '1.6', ['git']) + + def generate(self, source, destination, verbose=False, custom=None): + """Generate a fast import stream. See _Exporter.generate() for details.""" + args = ["git", "fast-export", "--all", "--signed-tags=warn"] + outf, base, marks = self.get_output_info(destination) + if marks: + marks = os.path.abspath(marks) + # Note: we don't pass import-marks because that creates + # a stream of incremental changes, not the full thing. + # We may support incremental output later ... + #if os.path.exists(marks): + # args.append('--import-marks=%s' % marks) + args.append('--export-marks=%s' % marks) + if custom: + args.extend(custom) + retcode = self.execute(args, outf, cwd=source) + self.report_results(retcode, destination) + + +class SubversionExporter(_Exporter): + + def __init__(self): + self.check_install('Python Subversion', '1.4', None, + ['svn.fs', 'svn.core', 'svn.repos']) + + def generate(self, source, destination, verbose=False, custom=None): + """Generate a fast import stream. See _Exporter.generate() for details.""" + args = ["svn-fast-export.py"] + outf, base, marks = self.get_output_info(destination) + # Marks aren't supported by svn-fast-export so no need to set that option + if custom: + args.extend(custom) + args.append(source) + retcode = self.execute_exporter_script(args, outf) + self.report_results(retcode, destination) + + +def fast_export_from(source, destination, tool, verbose=False, custom=None): + # Get the exporter + if tool == 'darcs': + factory = DarcsExporter + elif tool == 'hg': + factory = MercurialExporter + elif tool == 'git': + factory = GitExporter + elif tool == 'svn': + factory = SubversionExporter + try: + exporter = factory() + except MissingDependency, ex: + raise errors.BzrError(ex.get_message()) + + # Do the export + exporter.generate(source, destination, verbose=verbose, + custom=custom) diff --git a/exporters/hg-fast-export.py b/exporters/hg-fast-export.py index 9d0e2a2..45c9ab4 100755 --- a/exporters/hg-fast-export.py +++ b/exporters/hg-fast-export.py @@ -338,6 +338,12 @@ def mangle_mark(mark): def hg2git(repourl,m,marksfile,mappingfile,headsfile,tipfile,authors={},sob=False,force=False): _max=int(m) + try: + import msvcrt + msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) + except ImportError: + pass + marks_cache=load_cache(marksfile,mangle_mark) mapping_cache=load_cache(mappingfile) heads_cache=load_cache(headsfile) @@ -432,11 +438,5 @@ if __name__=='__main__': if options.origin_name!=None: set_origin_name(options.origin_name) - try: - import msvcrt - msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) - except ImportError: - pass - sys.exit(hg2git(options.repourl,m,options.marksfile,options.mappingfile,options.headsfile, options.statusfile,authors=a,sob=options.sob,force=options.force)) |