1 # Copyright (C) 2005-2007 Jelmer Vernooij <jelmer@samba.org>
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.
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 """Support for Subversion branches
19 Bazaar can be used with Subversion branches through the bzr-svn plugin.
21 Most Bazaar commands should work fine with Subversion branches. The following
22 commands at the moment do not:
25 - bzr push --overwrite
27 bzr-svn also adds two new commands to Bazaar:
32 For more information about bzr-svn, see the bzr-svn FAQ.
36 from bzrlib import log
37 from bzrlib.bzrdir import BzrDirFormat, format_registry
38 from bzrlib.errors import BzrError
39 from bzrlib.commands import Command, register_command, display_command, Option
40 from bzrlib.help_topics import topic_registry
41 from bzrlib.revisionspec import SPEC_TYPES
42 from bzrlib.trace import warning, mutter
43 from bzrlib.transport import register_lazy_transport, register_transport_proto
47 # versions ending in 'exp' mean experimental mappings
48 # versions ending in 'dev' mean development version
49 # versions ending in 'final' mean release (well tested, etc)
50 version_info = (0, 4, 11, 'exp', 0)
52 if version_info[3] == 'final':
53 version_string = '%d.%d.%d' % version_info[:3]
55 version_string = '%d.%d.%d%s%d' % version_info
56 __version__ = version_string
58 COMPATIBLE_BZR_VERSIONS = [(1, 6)]
60 def check_bzrlib_version(desired):
61 """Check that bzrlib is compatible.
63 If version is < all compatible version, assume incompatible.
64 If version is compatible version + 1, assume compatible, with deprecations
65 Otherwise, assume incompatible.
68 bzrlib_version = bzrlib.version_info[:2]
69 if (bzrlib_version in desired or
70 ((bzrlib_version[0], bzrlib_version[1]-1) in desired and
71 bzrlib.version_info[3] in ('dev', 'exp'))):
73 if bzrlib_version < desired[0]:
74 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]))
76 warning('bzr-svn is not up to date with installed bzr version %s.'
77 ' \nThere should be a newer version of bzr-svn available.',
79 if not (bzrlib_version[0], bzrlib_version[1]-1) in desired:
80 raise BzrError('Version mismatch')
82 def check_subversion_version():
83 """Check that Subversion is compatible.
87 (base, _) = os.path.splitext(m.__file__)
88 c_file = "%s.c" % base
89 if not os.path.exists(c_file):
91 if os.path.getmtime(m.__file__) < os.path.getmtime(c_file):
95 from bzrlib.plugins.svn import client, ra, repos, wc
96 for x in client, ra, repos, wc:
97 if not check_mtime(x):
98 warning("bzr-svn extensions are outdated and need to be rebuilt")
101 warning("Unable to load bzr-svn extensions - did you build it?")
103 ra_version = ra.version()
104 if (ra_version[0] >= 5 and getattr(ra, 'SVN_REVISION', None) and 27729 <= ra.SVN_REVISION < 31470):
105 warning('Installed Subversion has buggy svn.ra.get_log() implementation, please install newer.')
107 mutter("bzr-svn: using Subversion %d.%d.%d (%s)" % ra_version)
110 def check_rebase_version(min_version):
111 """Check what version of bzr-rebase is installed.
113 Raises an exception when the version installed is older than
116 :raises RebaseNotPresent: Raised if bzr-rebase is not installed or too old.
118 from bzrlib.plugins.svn.errors import RebaseNotPresent
120 from bzrlib.plugins.rebase import version_info as rebase_version_info
121 if rebase_version_info[:2] < min_version:
122 raise RebaseNotPresent("Version %r present, at least %r required"
123 % (rebase_version_info, min_version))
124 except ImportError, e:
125 raise RebaseNotPresent(e)
128 check_subversion_version()
130 from bzrlib.plugins.svn import format, revspec
132 register_transport_proto('svn+ssh://',
133 help="Access using the Subversion smart server tunneled over SSH.")
134 register_transport_proto('svn+file://',
135 help="Access of local Subversion repositories.")
136 register_transport_proto('svn+http://',
137 help="Access of Subversion smart servers over HTTP.")
138 register_transport_proto('svn+https://',
139 help="Access of Subversion smart servers over secure HTTP.")
140 register_transport_proto('svn://',
141 help="Access using the Subversion smart server.")
142 register_lazy_transport('svn://', 'bzrlib.plugins.svn.transport',
144 register_lazy_transport('svn+', 'bzrlib.plugins.svn.transport',
146 topic_registry.register_lazy('svn-branching-schemes',
147 'bzrlib.plugins.svn.mapping3.scheme',
148 'help_schemes', 'Subversion branching schemes')
150 BzrDirFormat.register_control_format(format.SvnRemoteFormat)
151 BzrDirFormat.register_control_format(format.SvnWorkingTreeDirFormat)
152 format_registry.register("subversion", format.SvnRemoteFormat,
153 "Subversion repository. ",
155 format_registry.register("subversion-wc", format.SvnWorkingTreeDirFormat,
156 "Subversion working copy. ",
157 native=False, hidden=True)
158 SPEC_TYPES.append(revspec.RevisionSpec_svn)
160 if getattr(log, "properties_handler_registry", None) is not None:
161 log.properties_handler_registry.register_lazy("subversion",
162 "bzrlib.plugins.svn.log",
163 "show_subversion_properties")
165 versions_checked = False
166 def lazy_check_versions():
167 """Check whether all dependencies have the right versions.
169 :note: Only checks once, caches the result."""
170 global versions_checked
173 versions_checked = True
174 check_bzrlib_version(COMPATIBLE_BZR_VERSIONS)
176 optimizers_registered = False
177 def lazy_register_optimizers():
178 """Register optimizers for fetching between Subversion and Bazaar
181 :note: Only registers on the first call."""
182 global optimizers_registered
183 if optimizers_registered:
185 from bzrlib.repository import InterRepository
186 from bzrlib.plugins.svn import commit, fetch
187 optimizers_registered = True
188 InterRepository.register_optimiser(fetch.InterFromSvnRepository)
189 InterRepository.register_optimiser(commit.InterToSvnRepository)
192 def get_scheme(schemename):
193 """Parse scheme identifier and return a branching scheme.
195 :param schemename: Name of the scheme to retrieve.
197 if isinstance(schemename, unicode):
198 schemename = schemename.encode("ascii")
199 from bzrlib.plugins.svn.mapping3.scheme import BranchingScheme
200 from bzrlib.errors import BzrCommandError
202 ret = BranchingScheme.find_scheme(schemename)
204 raise BzrCommandError('No such branching scheme %r' % schemename)
208 class cmd_svn_import(Command):
209 """Convert a Subversion repository to a Bazaar repository.
211 To save disk space, only branches will be created by default
212 (no working trees). To create a tree for a branch, run "bzr co" in
215 takes_args = ['from_location', 'to_location?']
216 takes_options = [Option('trees', help='Create working trees.'),
217 Option('standalone', help='Create standalone branches.'),
219 help='Convert all revisions, even those not in '
220 'current branch history (forbids --standalone).'),
221 Option('scheme', type=get_scheme,
222 help='Branching scheme (none, trunk, etc). '
224 Option('prefix', type=str,
225 help='Only consider branches of which path starts '
230 def run(self, from_location, to_location=None, trees=False,
231 standalone=False, scheme=None, all=False, prefix=None):
232 from bzrlib.branch import Branch
233 from bzrlib.bzrdir import BzrDir
234 from bzrlib.errors import BzrCommandError, NoRepositoryPresent, NotBranchError
235 from bzrlib import urlutils
236 from bzrlib.plugins.svn.convert import convert_repository
237 from bzrlib.plugins.svn.mapping3 import repository_guess_scheme
238 from bzrlib.plugins.svn.repository import SvnRepository
241 if to_location is None:
242 to_location = os.path.basename(from_location.rstrip("/\\"))
245 # All implies shared repository
246 # (otherwise there is no repository to store revisions in)
249 if os.path.isfile(from_location):
250 from bzrlib.plugins.svn.convert import load_dumpfile
252 tmp_repos = tempfile.mkdtemp(prefix='bzr-svn-dump-')
253 load_dumpfile(from_location, tmp_repos)
254 from_location = tmp_repos
258 from_dir = BzrDir.open(from_location)
260 from_repos = from_dir.open_repository()
261 except NoRepositoryPresent, e:
262 if prefix is not None:
263 raise BzrCommandError("Path inside repository specified and --prefix specified")
264 from_repos = from_dir.find_repository()
265 prefix = urlutils.relative_url(from_repos.base, from_location)
266 prefix = prefix.encode("utf-8")
268 from_repos.lock_read()
270 (guessed_scheme, scheme) = repository_guess_scheme(from_repos, from_repos.get_latest_revnum())
272 if prefix is not None:
273 prefix = prefix.strip("/") + "/"
274 if guessed_scheme.is_branch(prefix):
275 raise BzrCommandError("%s appears to contain a branch. "
276 "For individual branches, use 'bzr branch'." % from_location)
278 self.outf.write("Importing branches with prefix /%s\n" %
279 urlutils.unescape_for_display(prefix, self.outf.encoding))
281 if not isinstance(from_repos, SvnRepository):
282 raise BzrCommandError(
283 "Not a Subversion repository: %s" % from_location)
285 def filter_branch(branch):
286 if prefix is not None and not branch.get_branch_path().startswith(prefix):
290 convert_repository(from_repos, to_location, scheme, None,
291 not standalone, trees, all, filter_branch=filter_branch)
293 if tmp_repos is not None:
294 from bzrlib import osutils
295 osutils.rmtree(tmp_repos)
300 register_command(cmd_svn_import)
302 class cmd_svn_upgrade(Command):
303 """Upgrade revisions mapped from Subversion in a Bazaar branch.
305 This will change the revision ids of revisions whose parents
306 were mapped from svn revisions.
308 takes_args = ['from_repository?']
309 takes_options = ['verbose']
312 def run(self, from_repository=None, verbose=False):
313 from bzrlib.plugins.svn.upgrade import upgrade_branch, upgrade_workingtree
314 from bzrlib.branch import Branch
315 from bzrlib.errors import NoWorkingTree, BzrCommandError
316 from bzrlib.repository import Repository
317 from bzrlib.trace import info
318 from bzrlib.workingtree import WorkingTree
320 wt_to = WorkingTree.open(".")
321 branch_to = wt_to.branch
322 except NoWorkingTree:
324 branch_to = Branch.open(".")
326 stored_loc = branch_to.get_parent()
327 if from_repository is None:
328 if stored_loc is None:
329 raise BzrCommandError("No pull location known or"
332 import bzrlib.urlutils as urlutils
333 display_url = urlutils.unescape_for_display(stored_loc,
335 self.outf.write("Using saved location: %s\n" % display_url)
336 from_repository = Branch.open(stored_loc).repository
338 from_repository = Repository.open(from_repository)
340 if wt_to is not None:
341 renames = upgrade_workingtree(wt_to, from_repository,
342 allow_changes=True, verbose=verbose)
344 renames = upgrade_branch(branch_to, from_repository,
345 allow_changes=True, verbose=verbose)
348 info("Nothing to do.")
350 if wt_to is not None:
351 wt_to.set_last_revision(branch_to.last_revision())
353 register_command(cmd_svn_upgrade)
355 class cmd_svn_push(Command):
356 """Push revisions to Subversion, creating a new branch if necessary.
358 The behaviour of this command is the same as that of "bzr push", except
359 that it also creates new branches.
361 This command is experimental and will be removed in the future when all
362 functionality is included in "bzr push".
364 takes_args = ['location?']
365 takes_options = ['revision', 'remember', Option('directory',
366 help='Branch to push from, '
367 'rather than the one containing the working directory.',
372 def run(self, location=None, revision=None, remember=False,
374 from bzrlib.bzrdir import BzrDir
375 from bzrlib.branch import Branch
376 from bzrlib.errors import NotBranchError, BzrCommandError
377 from bzrlib import urlutils
379 if directory is None:
381 source_branch = Branch.open_containing(directory)[0]
382 stored_loc = source_branch.get_push_location()
384 if stored_loc is None:
385 raise BzrCommandError("No push location known or specified.")
387 display_url = urlutils.unescape_for_display(stored_loc,
389 self.outf.write("Using saved location: %s\n" % display_url)
390 location = stored_loc
392 source_branch.lock_read()
394 bzrdir = BzrDir.open(location)
395 if revision is not None:
396 if len(revision) > 1:
397 raise BzrCommandError(
398 'bzr svn-push --revision takes exactly one revision'
400 revision_id = revision[0].as_revision_id(source_branch)
404 target_branch = bzrdir.open_branch()
405 target_branch.lock_write()
407 target_branch.pull(source_branch, stop_revision=revision_id)
409 target_branch.unlock()
410 except NotBranchError:
411 target_branch = bzrdir.import_branch(source_branch, revision_id)
412 # We successfully created the target, remember it
413 if source_branch.get_push_location() is None or remember:
414 source_branch.set_push_location(target_branch.base)
416 source_branch.unlock()
418 register_command(cmd_svn_push)
420 class cmd_dpush(Command):
421 """Push diffs into Subversion avoiding the use of any Bazaar-specific properties.
423 This will afterwards rebase the local Bazaar branch on the Subversion
424 branch unless the --no-rebase option is used, in which case
425 the two branches will be out of sync.
427 takes_args = ['location?']
428 takes_options = ['remember', Option('directory',
429 help='Branch to push from, '
430 'rather than the one containing the working directory.',
434 Option('no-rebase', help="Don't rebase after push")]
436 def run(self, location=None, remember=False, directory=None, no_rebase=False):
437 from bzrlib import urlutils
438 from bzrlib.bzrdir import BzrDir
439 from bzrlib.branch import Branch
440 from bzrlib.errors import NotBranchError, BzrCommandError, NoWorkingTree
441 from bzrlib.workingtree import WorkingTree
443 from bzrlib.plugins.svn.commit import dpush
445 if directory is None:
448 source_wt = WorkingTree.open_containing(directory)[0]
449 source_branch = source_wt.branch
450 except NoWorkingTree:
451 source_branch = Branch.open_containing(directory)[0]
453 stored_loc = source_branch.get_push_location()
455 if stored_loc is None:
456 raise BzrCommandError("No push location known or specified.")
458 display_url = urlutils.unescape_for_display(stored_loc,
460 self.outf.write("Using saved location: %s\n" % display_url)
461 location = stored_loc
463 bzrdir = BzrDir.open(location)
464 target_branch = bzrdir.open_branch()
465 target_branch.lock_write()
466 revid_map = dpush(target_branch, source_branch)
467 # We successfully created the target, remember it
468 if source_branch.get_push_location() is None or remember:
469 source_branch.set_push_location(target_branch.base)
471 revno, old_last_revid = source_branch.last_revision_info()
472 new_last_revid = revid_map[old_last_revid]
473 if source_wt is not None:
474 source_wt.pull(target_branch, overwrite=True,
475 stop_revision=new_last_revid)
477 source_branch.pull(target_branch, overwrite=True,
478 stop_revision=new_last_revid)
481 register_command(cmd_dpush)
484 class cmd_svn_branching_scheme(Command):
485 """Show or change the branching scheme for a Subversion repository.
487 See 'bzr help svn-branching-schemes' for details.
489 takes_args = ['location?']
491 Option('set', help="Change the branching scheme. "),
492 Option('repository-wide',
493 help="Act on repository-wide setting rather than local.")
496 def run(self, location=".", set=False, repository_wide=False):
497 from bzrlib.bzrdir import BzrDir
498 from bzrlib.errors import BzrCommandError
499 from bzrlib.msgeditor import edit_commit_message
500 from bzrlib.repository import Repository
501 from bzrlib.trace import info
502 from bzrlib.plugins.svn.repository import SvnRepository
503 from bzrlib.plugins.svn.mapping3.scheme import scheme_from_branch_list
504 from bzrlib.plugins.svn.mapping3 import config_set_scheme, get_property_scheme, set_property_scheme
505 def scheme_str(scheme):
508 return "".join(map(lambda x: x+"\n", scheme.to_lines()))
509 dir = BzrDir.open_containing(location)[0]
510 repos = dir.find_repository()
511 if not isinstance(repos, SvnRepository):
512 raise BzrCommandError("Not a Subversion repository: %s" % location)
514 scheme = get_property_scheme(repos)
516 scheme = repos.get_mapping().scheme
518 schemestr = edit_commit_message("",
519 start_message=scheme_str(scheme))
520 scheme = scheme_from_branch_list(
521 map(lambda x:x.strip("\n"), schemestr.splitlines()))
523 set_property_scheme(repos, scheme)
525 config_set_scheme(repos, scheme, None, mandatory=True)
526 elif scheme is not None:
527 info(scheme_str(scheme))
530 register_command(cmd_svn_branching_scheme)
533 class cmd_svn_set_revprops(Command):
534 """Migrate Bazaar metadata to Subversion revision properties.
536 This requires that you have permission to change the
537 revision properties on the repository.
539 To change these permissions, edit the hooks/pre-revprop-change
540 file in the Subversion repository.
542 takes_args = ['location']
544 def run(self, location="."):
545 raise NotImplementedError(self.run)
548 register_command(cmd_svn_set_revprops)
552 """Returns the testsuite for bzr-svn."""
553 from unittest import TestSuite
554 from bzrlib.plugins.svn import tests
556 suite.addTest(tests.test_suite())
560 if __name__ == '__main__':
561 print ("This is a Bazaar plugin. Copy this directory to ~/.bazaar/plugins "
563 elif __name__ != 'bzrlib.plugins.svn':
564 raise ImportError('The Subversion plugin must be installed as'
565 ' bzrlib.plugins.svn not %s' % __name__)