Deal with newer versions of bzr passing in unicode strings.
[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 2 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, 9, '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 = [(0, 93), (1, 0), (1, 1), (1, 2)]
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.errors import BzrCommandError, NoRepositoryPresent
179         from bzrlib.bzrdir import BzrDir
180         from convert import convert_repository
181         from repository import SvnRepository
182         import os
183
184         if to_location is None:
185             to_location = os.path.basename(from_location.rstrip("/\\"))
186
187         if all:
188             # All implies shared repository 
189             # (otherwise there is no repository to store revisions in)
190             standalone = False
191
192         if os.path.isfile(from_location):
193             from convert import load_dumpfile
194             import tempfile
195             tmp_repos = tempfile.mkdtemp(prefix='bzr-svn-dump-')
196             load_dumpfile(from_location, tmp_repos)
197             from_location = tmp_repos
198         else:
199             tmp_repos = None
200
201         from_dir = BzrDir.open(from_location)
202         try:
203             from_repos = from_dir.open_repository()
204         except NoRepositoryPresent, e:
205             raise BzrCommandError("No Repository found at %s. "
206                 "For individual branches, use 'bzr branch'." % from_location)
207
208         if not isinstance(from_repos, SvnRepository):
209             raise BzrCommandError(
210                     "Not a Subversion repository: %s" % from_location)
211
212         def filter_branch((branch_path, revnum, exists)):
213             if prefix is not None and not branch_path.startswith(prefix):
214                 return False
215             return exists
216
217         convert_repository(from_repos, to_location, scheme, not standalone, 
218                 trees, all, filter_branch=filter_branch)
219
220         if tmp_repos is not None:
221             from bzrlib import osutils
222             osutils.rmtree(tmp_repos)
223
224
225 register_command(cmd_svn_import)
226
227 class cmd_svn_upgrade(Command):
228     """Upgrade revisions mapped from Subversion in a Bazaar branch.
229     
230     This will change the revision ids of revisions whose parents 
231     were mapped from svn revisions.
232     """
233     takes_args = ['from_repository?']
234     takes_options = ['verbose']
235
236     @display_command
237     def run(self, from_repository=None, verbose=False):
238         from upgrade import upgrade_branch, upgrade_workingtree
239         from bzrlib.branch import Branch
240         from bzrlib.errors import NoWorkingTree, BzrCommandError
241         from bzrlib.repository import Repository
242         from bzrlib.trace import info
243         from bzrlib.workingtree import WorkingTree
244         try:
245             wt_to = WorkingTree.open(".")
246             branch_to = wt_to.branch
247         except NoWorkingTree:
248             wt_to = None
249             branch_to = Branch.open(".")
250
251         stored_loc = branch_to.get_parent()
252         if from_repository is None:
253             if stored_loc is None:
254                 raise BzrCommandError("No pull location known or"
255                                              " specified.")
256             else:
257                 import bzrlib.urlutils as urlutils
258                 display_url = urlutils.unescape_for_display(stored_loc,
259                         self.outf.encoding)
260                 self.outf.write("Using saved location: %s\n" % display_url)
261                 from_repository = Branch.open(stored_loc).repository
262         else:
263             from_repository = Repository.open(from_repository)
264
265         if wt_to is not None:
266             renames = upgrade_workingtree(wt_to, from_repository, 
267                                           allow_changes=True, verbose=verbose)
268         else:
269             renames = upgrade_branch(branch_to, from_repository, 
270                                      allow_changes=True, verbose=verbose)
271
272         if renames == {}:
273             info("Nothing to do.")
274
275         if wt_to is not None:
276             wt_to.set_last_revision(branch_to.last_revision())
277
278 register_command(cmd_svn_upgrade)
279
280 class cmd_svn_push(Command):
281     """Push revisions to Subversion, creating a new branch if necessary.
282
283     The behaviour of this command is the same as that of "bzr push", except 
284     that it also creates new branches.
285     
286     This command is experimental and will be removed in the future when all 
287     functionality is included in "bzr push".
288     """
289     takes_args = ['location?']
290     takes_options = ['revision', 'remember', Option('directory',
291             help='Branch to push from, '
292                  'rather than the one containing the working directory.',
293             short_name='d',
294             type=unicode,
295             )]
296
297     def run(self, location=None, revision=None, remember=False, 
298             directory=None):
299         from bzrlib.bzrdir import BzrDir
300         from bzrlib.branch import Branch
301         from bzrlib.errors import NotBranchError, BzrCommandError
302         from bzrlib import urlutils
303
304         if directory is None:
305             directory = "."
306         source_branch = Branch.open_containing(directory)[0]
307         stored_loc = source_branch.get_push_location()
308         if location is None:
309             if stored_loc is None:
310                 raise BzrCommandError("No push location known or specified.")
311             else:
312                 display_url = urlutils.unescape_for_display(stored_loc,
313                         self.outf.encoding)
314                 self.outf.write("Using saved location: %s\n" % display_url)
315                 location = stored_loc
316
317         bzrdir = BzrDir.open(location)
318         if revision is not None:
319             if len(revision) > 1:
320                 raise BzrCommandError(
321                     'bzr svn-push --revision takes exactly one revision' 
322                     ' identifier')
323             revision_id = revision[0].in_history(source_branch).rev_id
324         else:
325             revision_id = None
326         try:
327             target_branch = bzrdir.open_branch()
328             target_branch.pull(source_branch, revision_id)
329         except NotBranchError:
330             target_branch = bzrdir.import_branch(source_branch, revision_id)
331         # We successfully created the target, remember it
332         if source_branch.get_push_location() is None or remember:
333             source_branch.set_push_location(target_branch.base)
334
335 register_command(cmd_svn_push)
336
337
338 class cmd_svn_branching_scheme(Command):
339     """Show or change the branching scheme for a Subversion repository.
340
341     See 'bzr help svn-branching-schemes' for details.
342     """
343     takes_args = ['location?']
344     takes_options = [
345         Option('set', help="Change the branching scheme. "),
346         Option('repository-wide', 
347             help="Act on repository-wide setting rather than local.")
348         ]
349
350     def run(self, location=".", set=False, repository_wide=False):
351         from bzrlib.bzrdir import BzrDir
352         from bzrlib.errors import BzrCommandError
353         from bzrlib.msgeditor import edit_commit_message
354         from bzrlib.repository import Repository
355         from bzrlib.trace import info
356         from repository import SvnRepository
357         from scheme import scheme_from_branch_list
358         def scheme_str(scheme):
359             if scheme is None:
360                 return ""
361             return "".join(map(lambda x: x+"\n", scheme.to_lines()))
362         dir = BzrDir.open_containing(location)[0]
363         repos = dir.find_repository()
364         if not isinstance(repos, SvnRepository):
365             raise BzrCommandError("Not a Subversion repository: %s" % location)
366         if repository_wide:
367             scheme = repos._get_property_scheme()
368         else:
369             scheme = repos.get_scheme()
370         if set:
371             schemestr = edit_commit_message("", 
372                                             start_message=scheme_str(scheme))
373             scheme = scheme_from_branch_list(
374                 map(lambda x:x.strip("\n"), schemestr.splitlines()))
375             if repository_wide:
376                 repos.set_property_scheme(scheme)
377             else:
378                 repos.set_branching_scheme(scheme, mandatory=True)
379         elif scheme is not None:
380             info(scheme_str(scheme))
381
382
383 register_command(cmd_svn_branching_scheme)
384
385
386 class cmd_svn_set_revprops(Command):
387     """Migrate Bazaar metadata to Subversion revision properties.
388
389     This requires that you have permission to change the 
390     revision properties on the repository.
391
392     To change these permissions, edit the hooks/pre-revprop-change 
393     file in the Subversion repository.
394     """
395     takes_args = ['location']
396
397     def run(self, location="."):
398         raise NotImplementedError(self.run)
399
400
401 register_command(cmd_svn_set_revprops)
402
403
404 def test_suite():
405     """Returns the testsuite for bzr-svn."""
406     from unittest import TestSuite
407     import tests
408     suite = TestSuite()
409     suite.addTest(tests.test_suite())
410     return suite
411
412
413 if __name__ == '__main__':
414     print ("This is a Bazaar plugin. Copy this directory to ~/.bazaar/plugins "
415           "to use it.\n")
416 elif __name__ != 'bzrlib.plugins.svn':
417     raise ImportError('The Subversion plugin must be installed as'
418                       ' bzrlib.plugins.svn not %s' % __name__)
419 else:
420     import os, sys
421     sys.path.append(os.path.dirname(os.path.abspath(__file__)))