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