1 # Copyright (C) 2009 Canonical Ltd
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software
15 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 """Simplified and unified access to the various xxx-fast-export tools."""
20 import gzip
, os
, subprocess
, sys
22 from bzrlib
import errors
23 from bzrlib
.trace
import note
, warning
26 class MissingDependency(Exception):
28 def __init__(self
, tool
, minimum_version
, missing
):
30 self
.minimum_version
= minimum_version
31 self
.missing
= missing
33 def get_message(self
):
34 return "%s missing. Please install %s %s or later and try again." % \
35 (self
.missing
, self
.tool
, self
.minimum_version
)
38 class _Exporter(object):
40 def check_install(self
, tool_name
, minimum_version
, required_commands
=None,
41 required_libraries
=None):
42 """Check dependencies are correctly installed.
44 :param tool_name: name of the tool
45 :param minimum_version: minimum version required
46 :param required_commands: list of commands that must be on the path
47 :param required_libraries: list of Python libraries that must be
49 :raises MissingDependency: if a required dependency is not found
51 self
.tool_name
= tool_name
52 self
.minimum_version
= minimum_version
54 for cmd
in required_commands
:
55 self
._check
_cmd
_available
(cmd
)
56 if required_libraries
:
57 for lib
in required_libraries
:
58 self
._check
_lib
_available
(lib
)
60 def _check_cmd_available(self
, cmd
):
62 if isinstance(cmd
, str):
66 retcode
= subprocess
.call(args
, stdout
=subprocess
.PIPE
,
67 stderr
=subprocess
.PIPE
)
69 raise MissingDependency(self
.tool_name
, self
.minimum_version
, cmd
)
71 def _check_lib_available(self
, lib
):
75 raise MissingDependency(self
.tool_name
, self
.minimum_version
, lib
)
77 def generate(self
, source
, destination
, verbose
=False, custom
=None):
78 """Generate a fast import stream.
80 :param source: the source filename or URL
81 :param destination: filename or '-' for standard output
82 :param verbose: if True, output additional diagnostics
83 :param custom: a list of custom options to be added to the
84 command line of the underlying scripts used. If an option
85 and its argument are to be separated by a space, pass them
88 raise NotImplementedError(self
.generate
)
90 def get_output_info(self
, dest
):
91 """Get the output streams/filenames given a destination filename.
93 :return: outf, basename, marks where
94 outf is a file-like object for storing the output,
95 basename is the name without the .fi and .gz prefixes
96 marks is the name of the marks file to use, if any
99 return sys
.stdout
, None, None
101 #if dest.endswith('.gz'):
102 # outf = gzip.open(dest, 'wb')
105 outf
= open(dest
, 'w')
107 if base
.endswith(".fi"):
109 marks
= "%s.marks" % (base
,)
110 return outf
, base
, marks
112 def execute(self
, args
, outf
, cwd
=None):
113 """Execute a command, capture the output and close files.
115 :param args: list of arguments making up the command
116 :param outf: a file-like object for storing the output,
117 :param cwd: current working directory to use
118 :return: the return code
121 note("Executing %s in directory %s ..." % (" ".join(args
), cwd
))
123 note("Executing %s ..." % (" ".join(args
),))
125 p
= subprocess
.Popen(args
, stdout
=outf
, cwd
=cwd
)
128 if outf
!= sys
.stdout
:
132 def report_results(self
, retcode
, destination
):
133 """Report whether the export succeeded or otherwise."""
135 note("Export to %s completed successfully." % (destination
,))
137 warning("Export to %s exited with error code %d."
138 % (destination
, retcode
))
140 def execute_exporter_script(self
, args
, outf
):
141 """Execute an exporter script, capturing the output.
143 The script must be a Python script under the exporters directory.
145 :param args: list of arguments making up the script, the first of
146 which is the script name relative to the exporters directory.
147 :param outf: a file-like object for storing the output,
148 :return: the return code
150 # Note: currently assume Python is on the path. We could work around
151 # this later (for Windows users say) by packaging the scripts as Python
152 # modules and calling their internals directly.
153 exporters_dir
= os
.path
.dirname(__file__
)
154 script_abspath
= os
.path
.join(exporters_dir
, args
[0])
155 actual_args
= ['python', script_abspath
] + args
[1:]
156 return self
.execute(actual_args
, outf
)
159 class CvsExporter(_Exporter
):
162 self
.check_install('cvs2svn', '2.30', ['cvs2bzr'])
163 self
.check_install('CVS', '1.11', ['cvs'])
165 def generate(self
, source
, destination
, verbose
=False, custom
=None):
166 """Generate a fast import stream. See _Exporter.generate() for details."""
167 # TODO: pass a custom cvs2bzr-default.options file as soon as
168 # cvs2bzr handles --options along with others.
169 args
= ["cvs2bzr", "--dumpfile", destination
]
170 outf
, base
, marks
= self
.get_output_info(destination
)
171 # Marks aren't supported by cvs2bzr so no need to set that option
175 retcode
= self
.execute(args
, outf
)
176 self
.report_results(retcode
, destination
)
179 class DarcsExporter(_Exporter
):
182 self
.check_install('Darcs', '2.2', [('darcs', '--version')])
184 def generate(self
, source
, destination
, verbose
=False, custom
=None):
185 """Generate a fast import stream. See _Exporter.generate() for details."""
186 args
= ["darcs/darcs-fast-export"]
187 outf
, base
, marks
= self
.get_output_info(destination
)
189 args
.append('--export-marks=%s' % marks
)
193 retcode
= self
.execute_exporter_script(args
, outf
)
194 self
.report_results(retcode
, destination
)
197 class MercurialExporter(_Exporter
):
200 self
.check_install('Mercurial', '1.2', None, ['mercurial'])
202 def generate(self
, source
, destination
, verbose
=False, custom
=None):
203 """Generate a fast import stream. See _Exporter.generate() for details."""
204 # XXX: Should we add --force here?
205 args
= ["hg-fast-export.py", "-r", source
, "-s"]
206 outf
, base
, marks
= self
.get_output_info(destination
)
208 args
.append('--marks=%s.marks' % (base
,))
209 args
.append('--mapping=%s.mapping' % (base
,))
210 args
.append('--heads=%s.heads' % (base
,))
211 args
.append('--status=%s.status' % (base
,))
214 retcode
= self
.execute_exporter_script(args
, outf
)
215 self
.report_results(retcode
, destination
)
218 class GitExporter(_Exporter
):
221 self
.cmd_name
= "git"
222 if sys
.platform
== 'win32':
223 self
.cmd_name
= "git.cmd"
224 self
.check_install('Git', '1.6', [self
.cmd_name
])
226 def generate(self
, source
, destination
, verbose
=False, custom
=None):
227 """Generate a fast import stream. See _Exporter.generate() for details."""
228 args
= [self
.cmd_name
, "fast-export", "--all", "--signed-tags=warn"]
229 outf
, base
, marks
= self
.get_output_info(destination
)
231 marks
= os
.path
.abspath(marks
)
232 # Note: we don't pass import-marks because that creates
233 # a stream of incremental changes, not the full thing.
234 # We may support incremental output later ...
235 #if os.path.exists(marks):
236 # args.append('--import-marks=%s' % marks)
237 args
.append('--export-marks=%s' % marks
)
240 retcode
= self
.execute(args
, outf
, cwd
=source
)
241 self
.report_results(retcode
, destination
)
244 class MonotoneExporter(_Exporter
):
247 self
.check_install('Monotone', '0.43', ['mnt'])
249 def generate(self
, source
, destination
, verbose
=False, custom
=None):
250 """Generate a fast import stream. See _Exporter.generate() for details."""
251 args
= ["mnt", "git_export"]
252 outf
, base
, marks
= self
.get_output_info(destination
)
254 marks
= os
.path
.abspath(marks
)
255 if os
.path
.exists(marks
):
256 args
.append('--import-marks=%s' % marks
)
257 args
.append('--export-marks=%s' % marks
)
260 retcode
= self
.execute(args
, outf
, cwd
=source
)
261 self
.report_results(retcode
, destination
)
264 class PerforceExporter(_Exporter
):
267 self
.check_install('p4', '2009.1', ['p4'])
268 self
.check_install('Perforce Python API', '2009.1', None, ['P4'])
269 self
.check_install('bzrp4', '', None, ['bzrlib.plugins.bzrp4'])
271 def generate(self
, source
, destination
, verbose
=False, custom
=None):
272 """Generate a fast import stream. See _Exporter.generate() for details."""
273 from bzrlib
.plugins
.bzrp4
import p4_fast_export
274 outf
, base
, marks
= self
.get_output_info(destination
)
275 # Marks aren't supported by p4_fast_export so no need to set that
277 original_stdout
= sys
.stdout
280 retcode
= p4_fast_export
.main([source
])
282 sys
.stdout
= original_stdout
283 self
.report_results(retcode
, destination
)
286 class SubversionExporter(_Exporter
):
289 self
.check_install('Python Subversion', '1.4', None,
290 ['svn.fs', 'svn.core', 'svn.repos'])
292 def generate(self
, source
, destination
, verbose
=False, custom
=None):
293 """Generate a fast import stream. See _Exporter.generate() for details."""
294 args
= ["svn-fast-export.py"]
295 outf
, base
, marks
= self
.get_output_info(destination
)
296 # Marks aren't supported by svn-fast-export so no need to set that option
300 retcode
= self
.execute_exporter_script(args
, outf
)
301 self
.report_results(retcode
, destination
)
304 def fast_export_from(source
, destination
, tool
, verbose
=False, custom
=None):
307 factory
= CvsExporter
308 elif tool
== 'darcs':
309 factory
= DarcsExporter
311 factory
= MercurialExporter
313 factory
= GitExporter
315 factory
= MonotoneExporter
317 factory
= PerforceExporter
319 factory
= SubversionExporter
322 except MissingDependency
, ex
:
323 raise errors
.BzrError(ex
.get_message())
326 exporter
.generate(source
, destination
, verbose
=verbose
,