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