Merge 0.4.
[jelmer/subvertpy.git] / __init__.py
1 # Copyright (C) 2005-2007 Jelmer Vernooij <jelmer@samba.org>
2
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 3 of the License, or
6 # (at your option) any later version.
7
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.
12
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
16
17 """
18 Support for Subversion branches
19 """
20 import bzrlib
21 from bzrlib.bzrdir import BzrDirFormat, format_registry
22 from bzrlib.commands import Command, register_command, display_command, Option
23 from bzrlib.help_topics import topic_registry
24 from bzrlib.revisionspec import SPEC_TYPES
25 from bzrlib.trace import warning
26 from bzrlib.transport import register_lazy_transport, register_transport_proto
27
28 import format
29 import revspec
30
31 # versions ending in 'exp' mean experimental mappings
32 # versions ending in 'dev' mean development version
33 # versions ending in 'final' mean release (well tested, etc)
34 version_info = (0, 4, 10, 'dev', 0)
35
36 if version_info[3] == 'final':
37     version_string = '%d.%d.%d' % version_info[:3]
38 else:
39     version_string = '%d.%d.%d%s%d' % version_info
40 __version__ = version_string
41
42 COMPATIBLE_BZR_VERSIONS = [(1, 3)]
43
44 def check_bzrlib_version(desired):
45     """Check that bzrlib is compatible.
46
47     If version is < all compatible version, assume incompatible.
48     If version is compatible version + 1, assume compatible, with deprecations
49     Otherwise, assume incompatible.
50     """
51     import bzrlib
52     bzrlib_version = bzrlib.version_info[:2]
53     if (bzrlib_version in desired or 
54         ((bzrlib_version[0], bzrlib_version[1]-1) in desired and 
55          bzrlib.version_info[3] == 'dev')):
56         return
57     from bzrlib.errors import BzrError
58     if bzrlib_version < desired[0]:
59         raise BzrError('Installed bzr version %s is too old to be used with bzr-svn, at least %s.%s required' % (bzrlib.__version__, desired[0][0], desired[0][1]))
60     else:
61         warning('bzr-svn is not up to date with installed bzr version %s.'
62                 ' \nThere should be a newer version of bzr-svn available.' 
63                 % (bzrlib.__version__))
64         if not (bzrlib_version[0], bzrlib_version[1]-1) in desired:
65             raise BzrError('Version mismatch')
66
67 def check_subversion_version():
68     """Check that Subversion is compatible.
69
70     """
71     try:
72         import svn.delta
73     except ImportError:
74         warning('No Python bindings for Subversion installed. See the '
75                 'bzr-svn README for details.')
76         raise bzrlib.errors.BzrError("missing python subversion bindings")
77     if (not hasattr(svn.delta, 'svn_delta_invoke_txdelta_window_handler') and 
78         not hasattr(svn.delta, 'tx_invoke_window_handler')):
79         warning('Installed Subversion version does not have updated Python '
80                 'bindings. See the bzr-svn README for details.')
81         raise bzrlib.errors.BzrError("incompatible python subversion bindings")
82
83 check_subversion_version()
84
85 register_transport_proto('svn+ssh://', 
86     help="Access using the Subversion smart server tunneled over SSH.")
87 register_transport_proto('svn+file://', 
88     help="Access of local Subversion repositories.")
89 register_transport_proto('svn+http://',
90     help="Access of Subversion smart servers over HTTP.")
91 register_transport_proto('svn+https://',
92     help="Access of Subversion smart servers over secure HTTP.")
93 register_transport_proto('svn://', 
94     help="Access using the Subversion smart server.")
95 register_lazy_transport('svn://', 'bzrlib.plugins.svn.transport', 
96                         'SvnRaTransport')
97 register_lazy_transport('svn+', 'bzrlib.plugins.svn.transport', 
98                         'SvnRaTransport')
99 topic_registry.register_lazy('svn-branching-schemes', 
100                              'bzrlib.plugins.svn.scheme',
101                              'help_schemes', 'Subversion branching schemes')
102
103 BzrDirFormat.register_control_format(format.SvnRemoteFormat)
104 BzrDirFormat.register_control_format(format.SvnWorkingTreeDirFormat)
105 format_registry.register("subversion", format.SvnRemoteFormat, 
106                          "Subversion repository. ", 
107                          native=False)
108 format_registry.register("subversion-wc", format.SvnWorkingTreeDirFormat, 
109                          "Subversion working copy. ", 
110                          native=False, hidden=True)
111 SPEC_TYPES.append(revspec.RevisionSpec_svn)
112
113 versions_checked = False
114 def lazy_check_versions():
115     """Check whether all dependencies have the right versions.
116     
117     :note: Only checks once, caches the result."""
118     global versions_checked
119     if versions_checked:
120         return
121     versions_checked = True
122     check_bzrlib_version(COMPATIBLE_BZR_VERSIONS)
123
124 optimizers_registered = False
125 def lazy_register_optimizers():
126     """Register optimizers for fetching between Subversion and Bazaar 
127     repositories.
128     
129     :note: Only registers on the first call."""
130     global optimizers_registered
131     if optimizers_registered:
132         return
133     from bzrlib.repository import InterRepository
134     import commit
135     import fetch
136     optimizers_registered = True
137     InterRepository.register_optimiser(fetch.InterFromSvnRepository)
138     InterRepository.register_optimiser(commit.InterToSvnRepository)
139
140
141 def get_scheme(schemename):
142     """Parse scheme identifier and return a branching scheme.
143     
144     :param schemename: Name of the scheme to retrieve.
145     """
146     if isinstance(schemename, unicode):
147         schemename = schemename.encode("ascii")
148     from scheme import BranchingScheme
149     from bzrlib.errors import BzrCommandError
150     
151     ret = BranchingScheme.find_scheme(schemename)
152     if ret is None:
153         raise BzrCommandError('No such branching scheme %r' % schemename)
154     return ret
155
156
157 class cmd_svn_import(Command):
158     """Convert a Subversion repository to a Bazaar repository.
159     
160     """
161     takes_args = ['from_location', 'to_location?']
162     takes_options = [Option('trees', help='Create working trees.'),
163                      Option('standalone', help='Create standalone branches.'),
164                      Option('all', 
165                          help='Convert all revisions, even those not in '
166                               'current branch history (forbids --standalone).'),
167                      Option('scheme', type=get_scheme,
168                          help='Branching scheme (none, trunk, etc). '
169                               'Default: auto.'),
170                      Option('prefix', type=str, 
171                          help='Only consider branches of which path starts '
172                               'with prefix.')
173                     ]
174
175     @display_command
176     def run(self, from_location, to_location=None, trees=False, 
177             standalone=False, scheme=None, all=False, prefix=None):
178         from bzrlib.branch import Branch
179         from bzrlib.bzrdir import BzrDir
180         from bzrlib.errors import BzrCommandError, NoRepositoryPresent, NotBranchError
181         from bzrlib import urlutils
182         from convert import convert_repository
183         from repository import SvnRepository
184         import os
185
186         if to_location is None:
187             to_location = os.path.basename(from_location.rstrip("/\\"))
188
189         if all:
190             # All implies shared repository 
191             # (otherwise there is no repository to store revisions in)
192             standalone = False
193
194         if os.path.isfile(from_location):
195             from convert import load_dumpfile
196             import tempfile
197             tmp_repos = tempfile.mkdtemp(prefix='bzr-svn-dump-')
198             load_dumpfile(from_location, tmp_repos)
199             from_location = tmp_repos
200         else:
201             tmp_repos = None
202
203         from_dir = BzrDir.open(from_location)
204         try:
205             from_repos = from_dir.open_repository()
206         except NoRepositoryPresent, e:
207             try:
208                 Branch.open(from_location)
209                 raise BzrCommandError("No Repository found at %s. "
210                     "For individual branches, use 'bzr branch'." % from_location)
211             except NotBranchError:
212                 if prefix is not None:
213                     raise BzrCommandError("Path inside repository specified and --prefix specified")
214                 from_repos = from_dir.find_repository()
215                 prefix = urlutils.relative_url(from_repos.base, from_location)
216                 self.outf.write("Importing branches below %s\n" % 
217                         urlutils.unescape_for_display(prefix, self.outf.encoding))
218
219         if prefix is not None:
220             prefix = prefix.strip("/") + "/"
221
222         if not isinstance(from_repos, SvnRepository):
223             raise BzrCommandError(
224                     "Not a Subversion repository: %s" % from_location)
225
226         def filter_branch((branch_path, revnum, exists)):
227             if prefix is not None and not branch_path.startswith(prefix):
228                 return False
229             return exists
230
231         convert_repository(from_repos, to_location, scheme, not standalone, 
232                 trees, all, filter_branch=filter_branch)
233
234         if tmp_repos is not None:
235             from bzrlib import osutils
236             osutils.rmtree(tmp_repos)
237
238
239 register_command(cmd_svn_import)
240
241 class cmd_svn_upgrade(Command):
242     """Upgrade revisions mapped from Subversion in a Bazaar branch.
243     
244     This will change the revision ids of revisions whose parents 
245     were mapped from svn revisions.
246     """
247     takes_args = ['from_repository?']
248     takes_options = ['verbose']
249
250     @display_command
251     def run(self, from_repository=None, verbose=False):
252         from upgrade import upgrade_branch, upgrade_workingtree
253         from bzrlib.branch import Branch
254         from bzrlib.errors import NoWorkingTree, BzrCommandError
255         from bzrlib.repository import Repository
256         from bzrlib.trace import info
257         from bzrlib.workingtree import WorkingTree
258         try:
259             wt_to = WorkingTree.open(".")
260             branch_to = wt_to.branch
261         except NoWorkingTree:
262             wt_to = None
263             branch_to = Branch.open(".")
264
265         stored_loc = branch_to.get_parent()
266         if from_repository is None:
267             if stored_loc is None:
268                 raise BzrCommandError("No pull location known or"
269                                              " specified.")
270             else:
271                 import bzrlib.urlutils as urlutils
272                 display_url = urlutils.unescape_for_display(stored_loc,
273                         self.outf.encoding)
274                 self.outf.write("Using saved location: %s\n" % display_url)
275                 from_repository = Branch.open(stored_loc).repository
276         else:
277             from_repository = Repository.open(from_repository)
278
279         if wt_to is not None:
280             renames = upgrade_workingtree(wt_to, from_repository, 
281                                           allow_changes=True, verbose=verbose)
282         else:
283             renames = upgrade_branch(branch_to, from_repository, 
284                                      allow_changes=True, verbose=verbose)
285
286         if renames == {}:
287             info("Nothing to do.")
288
289         if wt_to is not None:
290             wt_to.set_last_revision(branch_to.last_revision())
291
292 register_command(cmd_svn_upgrade)
293
294 class cmd_svn_push(Command):
295     """Push revisions to Subversion, creating a new branch if necessary.
296
297     The behaviour of this command is the same as that of "bzr push", except 
298     that it also creates new branches.
299     
300     This command is experimental and will be removed in the future when all 
301     functionality is included in "bzr push".
302     """
303     takes_args = ['location?']
304     takes_options = ['revision', 'remember', Option('directory',
305             help='Branch to push from, '
306                  'rather than the one containing the working directory.',
307             short_name='d',
308             type=unicode,
309             )]
310
311     def run(self, location=None, revision=None, remember=False, 
312             directory=None):
313         from bzrlib.bzrdir import BzrDir
314         from bzrlib.branch import Branch
315         from bzrlib.errors import NotBranchError, BzrCommandError
316         from bzrlib import urlutils
317
318         if directory is None:
319             directory = "."
320         source_branch = Branch.open_containing(directory)[0]
321         stored_loc = source_branch.get_push_location()
322         if location is None:
323             if stored_loc is None:
324                 raise BzrCommandError("No push location known or specified.")
325             else:
326                 display_url = urlutils.unescape_for_display(stored_loc,
327                         self.outf.encoding)
328                 self.outf.write("Using saved location: %s\n" % display_url)
329                 location = stored_loc
330
331         bzrdir = BzrDir.open(location)
332         if revision is not None:
333             if len(revision) > 1:
334                 raise BzrCommandError(
335                     'bzr svn-push --revision takes exactly one revision' 
336                     ' identifier')
337             revision_id = revision[0].in_history(source_branch).rev_id
338         else:
339             revision_id = None
340         try:
341             target_branch = bzrdir.open_branch()
342             target_branch.pull(source_branch, revision_id)
343         except NotBranchError:
344             target_branch = bzrdir.import_branch(source_branch, revision_id)
345         # We successfully created the target, remember it
346         if source_branch.get_push_location() is None or remember:
347             source_branch.set_push_location(target_branch.base)
348
349 register_command(cmd_svn_push)
350
351
352 class cmd_svn_branching_scheme(Command):
353     """Show or change the branching scheme for a Subversion repository.
354
355     See 'bzr help svn-branching-schemes' for details.
356     """
357     takes_args = ['location?']
358     takes_options = [
359         Option('set', help="Change the branching scheme. "),
360         Option('repository-wide', 
361             help="Act on repository-wide setting rather than local.")
362         ]
363
364     def run(self, location=".", set=False, repository_wide=False):
365         from bzrlib.bzrdir import BzrDir
366         from bzrlib.errors import BzrCommandError
367         from bzrlib.msgeditor import edit_commit_message
368         from bzrlib.repository import Repository
369         from bzrlib.trace import info
370         from repository import SvnRepository
371         from scheme import scheme_from_branch_list
372         def scheme_str(scheme):
373             if scheme is None:
374                 return ""
375             return "".join(map(lambda x: x+"\n", scheme.to_lines()))
376         dir = BzrDir.open_containing(location)[0]
377         repos = dir.find_repository()
378         if not isinstance(repos, SvnRepository):
379             raise BzrCommandError("Not a Subversion repository: %s" % location)
380         if repository_wide:
381             scheme = repos._get_property_scheme()
382         else:
383             scheme = repos.get_scheme()
384         if set:
385             schemestr = edit_commit_message("", 
386                                             start_message=scheme_str(scheme))
387             scheme = scheme_from_branch_list(
388                 map(lambda x:x.strip("\n"), schemestr.splitlines()))
389             if repository_wide:
390                 repos.set_property_scheme(scheme)
391             else:
392                 repos.set_branching_scheme(scheme, mandatory=True)
393         elif scheme is not None:
394             info(scheme_str(scheme))
395
396
397 register_command(cmd_svn_branching_scheme)
398
399
400 class cmd_svn_set_revprops(Command):
401     """Migrate Bazaar metadata to Subversion revision properties.
402
403     This requires that you have permission to change the 
404     revision properties on the repository.
405
406     To change these permissions, edit the hooks/pre-revprop-change 
407     file in the Subversion repository.
408     """
409     takes_args = ['location']
410
411     def run(self, location="."):
412         raise NotImplementedError(self.run)
413
414
415 register_command(cmd_svn_set_revprops)
416
417
418 def test_suite():
419     """Returns the testsuite for bzr-svn."""
420     from unittest import TestSuite
421     import tests
422     suite = TestSuite()
423     suite.addTest(tests.test_suite())
424     return suite
425
426
427 if __name__ == '__main__':
428     print ("This is a Bazaar plugin. Copy this directory to ~/.bazaar/plugins "
429           "to use it.\n")
430 elif __name__ != 'bzrlib.plugins.svn':
431     raise ImportError('The Subversion plugin must be installed as'
432                       ' bzrlib.plugins.svn not %s' % __name__)
433 else:
434     import os, sys
435     sys.path.append(os.path.dirname(os.path.abspath(__file__)))