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. To
22 create new branches in Subversion using push, it is currently necessary
23 to use the svn-push command rather than the standard push command.
25 bzr-svn also adds two new commands to Bazaar:
30 For more information about bzr-svn, see the bzr-svn FAQ.
34 from bzrlib import log
35 from bzrlib.bzrdir import BzrDirFormat, format_registry
36 from bzrlib.errors import BzrError
37 from bzrlib.commands import Command, register_command, display_command, Option
38 from bzrlib.help_topics import topic_registry
39 from bzrlib.revisionspec import SPEC_TYPES
40 from bzrlib.trace import warning, mutter
41 from bzrlib.transport import register_lazy_transport, register_transport_proto
45 # versions ending in 'exp' mean experimental mappings
46 # versions ending in 'dev' mean development version
47 # versions ending in 'final' mean release (well tested, etc)
48 version_info = (0, 4, 12, 'dev', 0)
50 if version_info[3] == 'final':
51 version_string = '%d.%d.%d' % version_info[:3]
53 version_string = '%d.%d.%d%s%d' % version_info
54 __version__ = version_string
56 COMPATIBLE_BZR_VERSIONS = [(1, 6)]
58 def check_bzrlib_version(desired):
59 """Check that bzrlib is compatible.
61 If version is < all compatible version, assume incompatible.
62 If version is compatible version + 1, assume compatible, with deprecations
63 Otherwise, assume incompatible.
65 bzrlib_version = bzrlib.version_info[:2]
66 if (bzrlib_version in desired or
67 ((bzrlib_version[0], bzrlib_version[1]-1) in desired and
68 bzrlib.version_info[3] in ('dev', 'exp'))):
70 if bzrlib_version < desired[0]:
71 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]))
73 warning('bzr-svn is not up to date with installed bzr version %s.'
74 ' \nThere should be a newer version of bzr-svn available.',
76 if not (bzrlib_version[0], bzrlib_version[1]-1) in desired:
77 raise BzrError('Version mismatch')
79 def check_subversion_version():
80 """Check that Subversion is compatible.
84 """Check whether a C extension is out of date."""
85 (base, _) = os.path.splitext(m.__file__)
86 c_file = "%s.c" % base
87 if not os.path.exists(c_file):
89 if os.path.getmtime(m.__file__) < os.path.getmtime(c_file):
93 from bzrlib.plugins.svn import client, ra, repos, wc
94 for x in client, ra, repos, wc:
95 if not check_mtime(x):
96 warning("bzr-svn extensions are outdated and need to be rebuilt")
99 warning("Unable to load bzr-svn extensions - did you build it?")
101 ra_version = ra.version()
102 if (ra_version[0] >= 5 and getattr(ra, 'SVN_REVISION', None) and
103 27729 <= ra.SVN_REVISION < 31470):
104 warning('Installed Subversion has buggy svn.ra.get_log() '
105 '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 log.properties_handler_registry.register_lazy("subversion",
161 "bzrlib.plugins.svn.log",
162 "show_subversion_properties")
164 _versions_checked = False
165 def lazy_check_versions():
166 """Check whether all dependencies have the right versions.
168 :note: Only checks once, caches the result."""
169 global _versions_checked
170 if _versions_checked:
172 _versions_checked = True
173 check_bzrlib_version(COMPATIBLE_BZR_VERSIONS)
175 _optimizers_registered = False
176 def lazy_register_optimizers():
177 """Register optimizers for fetching between Subversion and Bazaar
180 :note: Only registers on the first call."""
181 global _optimizers_registered
182 if _optimizers_registered:
184 from bzrlib.repository import InterRepository
185 from bzrlib.plugins.svn import commit, fetch
186 _optimizers_registered = True
187 InterRepository.register_optimiser(fetch.InterFromSvnRepository)
188 InterRepository.register_optimiser(commit.InterToSvnRepository)
191 def get_scheme(schemename):
192 """Parse scheme identifier and return a branching scheme.
194 :param schemename: Name of the scheme to retrieve.
196 if isinstance(schemename, unicode):
197 schemename = schemename.encode("ascii")
198 from bzrlib.plugins.svn.mapping3.scheme import BranchingScheme
199 from bzrlib.errors import BzrCommandError
201 ret = BranchingScheme.find_scheme(schemename)
203 raise BzrCommandError('No such branching scheme %r' % schemename)
207 class cmd_svn_import(Command):
208 """Convert a Subversion repository to a Bazaar repository.
210 To save disk space, only branches will be created by default
211 (no working trees). To create a tree for a branch, run "bzr co" in
214 takes_args = ['from_location', 'to_location?']
215 takes_options = [Option('trees', help='Create working trees.'),
216 Option('standalone', help='Create standalone branches.'),
218 help='Convert all revisions, even those not in '
219 'current branch history (forbids --standalone).'),
220 Option('scheme', type=get_scheme,
221 help='Branching scheme (none, trunk, etc). '
224 help="Don't delete branches removed in Subversion."),
225 Option('incremental',
226 help="Import revisions incrementally."),
227 Option('prefix', type=str,
228 help='Only consider branches of which path starts '
233 def run(self, from_location, to_location=None, trees=False,
234 standalone=False, scheme=None, all=False, prefix=None, keep=False,
236 from bzrlib.bzrdir import BzrDir
237 from bzrlib.errors import BzrCommandError, NoRepositoryPresent
238 from bzrlib import urlutils
239 from bzrlib.plugins.svn.convert import convert_repository
240 from bzrlib.plugins.svn.mapping3 import repository_guess_scheme
241 from bzrlib.plugins.svn.repository import SvnRepository
243 if to_location is None:
244 to_location = os.path.basename(from_location.rstrip("/\\"))
247 # All implies shared repository
248 # (otherwise there is no repository to store revisions in)
251 if os.path.isfile(from_location):
252 from bzrlib.plugins.svn.convert import load_dumpfile
254 tmp_repos = tempfile.mkdtemp(prefix='bzr-svn-dump-')
255 load_dumpfile(from_location, tmp_repos)
256 from_location = tmp_repos
260 from_dir = BzrDir.open(from_location)
262 from_repos = from_dir.open_repository()
263 except NoRepositoryPresent, e:
264 if prefix is not None:
265 raise BzrCommandError("Path inside repository specified "
266 "and --prefix specified")
267 from_repos = from_dir.find_repository()
268 prefix = urlutils.relative_url(from_repos.base, from_location)
269 prefix = prefix.encode("utf-8")
271 from_repos.lock_read()
273 (guessed_scheme, scheme) = repository_guess_scheme(from_repos,
274 from_repos.get_latest_revnum())
276 if prefix is not None:
277 prefix = prefix.strip("/") + "/"
278 if guessed_scheme.is_branch(prefix):
279 raise BzrCommandError("%s appears to contain a branch. "
280 "For individual branches, use 'bzr branch'." %
283 self.outf.write("Importing branches with prefix /%s\n" %
284 urlutils.unescape_for_display(prefix, self.outf.encoding))
286 if not isinstance(from_repos, SvnRepository):
287 raise BzrCommandError(
288 "Not a Subversion repository: %s" % from_location)
290 def filter_branch(branch):
291 if (prefix is not None and
292 not branch.get_branch_path().startswith(prefix)):
296 convert_repository(from_repos, to_location, scheme, None,
297 not standalone, trees, all,
298 filter_branch=filter_branch,
299 keep=keep, incremental=incremental)
301 if tmp_repos is not None:
302 from bzrlib import osutils
303 osutils.rmtree(tmp_repos)
308 register_command(cmd_svn_import)
310 class cmd_svn_upgrade(Command):
311 """Upgrade revisions mapped from Subversion in a Bazaar branch.
313 This will change the revision ids of revisions whose parents
314 were mapped from svn revisions.
316 takes_args = ['from_repository?']
317 takes_options = ['verbose']
320 def run(self, from_repository=None, verbose=False):
321 from bzrlib.plugins.svn.upgrade import (upgrade_branch,
323 from bzrlib.branch import Branch
324 from bzrlib.errors import NoWorkingTree, BzrCommandError
325 from bzrlib.repository import Repository
326 from bzrlib.trace import info
327 from bzrlib.workingtree import WorkingTree
329 wt_to = WorkingTree.open(".")
330 branch_to = wt_to.branch
331 except NoWorkingTree:
333 branch_to = Branch.open(".")
335 stored_loc = branch_to.get_parent()
336 if from_repository is None:
337 if stored_loc is None:
338 raise BzrCommandError("No pull location known or"
341 import bzrlib.urlutils as urlutils
342 display_url = urlutils.unescape_for_display(stored_loc,
344 self.outf.write("Using saved location: %s\n" % display_url)
345 from_repository = Branch.open(stored_loc).repository
347 from_repository = Repository.open(from_repository)
349 if wt_to is not None:
350 renames = upgrade_workingtree(wt_to, from_repository,
351 allow_changes=True, verbose=verbose)
353 renames = upgrade_branch(branch_to, from_repository,
354 allow_changes=True, verbose=verbose)
357 info("Nothing to do.")
359 if wt_to is not None:
360 wt_to.set_last_revision(branch_to.last_revision())
362 register_command(cmd_svn_upgrade)
364 class cmd_svn_push(Command):
365 """Push revisions to Subversion, creating a new branch if necessary.
367 The behaviour of this command is the same as that of "bzr push", except
368 that it also creates new branches.
370 This command is experimental and will be removed in the future when all
371 functionality is included in "bzr push".
373 takes_args = ['location?']
374 takes_options = ['revision', 'remember', Option('directory',
375 help='Branch to push from, '
376 'rather than the one containing the working directory.',
380 Option("merged", help="Push merged (right hand side) revisions.")]
382 def run(self, location=None, revision=None, remember=False,
383 directory=None, merged=None):
384 from bzrlib.bzrdir import BzrDir
385 from bzrlib.branch import Branch
386 from bzrlib.errors import NotBranchError, BzrCommandError
387 from bzrlib import urlutils
389 if directory is None:
391 source_branch = Branch.open_containing(directory)[0]
392 stored_loc = source_branch.get_push_location()
394 if stored_loc is None:
395 raise BzrCommandError("No push location known or specified.")
397 display_url = urlutils.unescape_for_display(stored_loc,
399 self.outf.write("Using saved location: %s\n" % display_url)
400 location = stored_loc
402 source_branch.lock_read()
404 bzrdir = BzrDir.open(location)
405 if revision is not None:
406 if len(revision) > 1:
407 raise BzrCommandError(
408 'bzr svn-push --revision takes exactly one revision'
410 revision_id = revision[0].as_revision_id(source_branch)
414 target_branch = bzrdir.open_branch()
415 target_branch.lock_write()
417 target_branch.pull(source_branch, stop_revision=revision_id, _push_merged=merged)
419 target_branch.unlock()
420 except NotBranchError:
421 target_branch = bzrdir.import_branch(source_branch, revision_id, _push_merged=merged)
423 source_branch.unlock()
424 # We successfully created the target, remember it
425 if source_branch.get_push_location() is None or remember:
426 source_branch.set_push_location(target_branch.base)
428 register_command(cmd_svn_push)
430 class cmd_dpush(Command):
431 """Push diffs into Subversion without any Bazaar-specific properties set.
433 This will afterwards rebase the local Bazaar branch on the Subversion
434 branch unless the --no-rebase option is used, in which case
435 the two branches will be out of sync.
437 takes_args = ['location?']
438 takes_options = ['remember', Option('directory',
439 help='Branch to push from, '
440 'rather than the one containing the working directory.',
444 Option('no-rebase', help="Don't rebase after push")]
446 def run(self, location=None, remember=False, directory=None,
448 from bzrlib import urlutils
449 from bzrlib.bzrdir import BzrDir
450 from bzrlib.branch import Branch
451 from bzrlib.errors import BzrCommandError, NoWorkingTree
452 from bzrlib.workingtree import WorkingTree
454 from bzrlib.plugins.svn.commit import dpush
456 if directory is None:
459 source_wt = WorkingTree.open_containing(directory)[0]
460 source_branch = source_wt.branch
461 except NoWorkingTree:
462 source_branch = Branch.open_containing(directory)[0]
464 stored_loc = source_branch.get_push_location()
466 if stored_loc is None:
467 raise BzrCommandError("No push location known or specified.")
469 display_url = urlutils.unescape_for_display(stored_loc,
471 self.outf.write("Using saved location: %s\n" % display_url)
472 location = stored_loc
474 bzrdir = BzrDir.open(location)
475 target_branch = bzrdir.open_branch()
476 target_branch.lock_write()
477 revid_map = dpush(target_branch, source_branch)
478 # We successfully created the target, remember it
479 if source_branch.get_push_location() is None or remember:
480 source_branch.set_push_location(target_branch.base)
482 _, old_last_revid = source_branch.last_revision_info()
483 new_last_revid = revid_map[old_last_revid]
484 if source_wt is not None:
485 source_wt.pull(target_branch, overwrite=True,
486 stop_revision=new_last_revid)
488 source_branch.pull(target_branch, overwrite=True,
489 stop_revision=new_last_revid)
492 register_command(cmd_dpush)
495 class cmd_svn_branching_scheme(Command):
496 """Show or change the branching scheme for a Subversion repository.
498 See 'bzr help svn-branching-schemes' for details.
500 takes_args = ['location?']
502 Option('set', help="Change the branching scheme. "),
503 Option('repository-wide',
504 help="Act on repository-wide setting rather than local.")
507 def run(self, location=".", set=False, repository_wide=False):
508 from bzrlib.bzrdir import BzrDir
509 from bzrlib.errors import BzrCommandError
510 from bzrlib.msgeditor import edit_commit_message
511 from bzrlib.trace import info
512 from bzrlib.plugins.svn.repository import SvnRepository
513 from bzrlib.plugins.svn.mapping3.scheme import scheme_from_branch_list
514 from bzrlib.plugins.svn.mapping3 import (config_set_scheme,
515 get_property_scheme, set_property_scheme)
516 def scheme_str(scheme):
519 return "".join(map(lambda x: x+"\n", scheme.to_lines()))
520 dir = BzrDir.open_containing(location)[0]
521 repos = dir.find_repository()
522 if not isinstance(repos, SvnRepository):
523 raise BzrCommandError("Not a Subversion repository: %s" % location)
525 scheme = get_property_scheme(repos)
527 scheme = repos.get_mapping().scheme
529 schemestr = edit_commit_message("",
530 start_message=scheme_str(scheme))
531 scheme = scheme_from_branch_list(
532 map(lambda x:x.strip("\n"), schemestr.splitlines()))
534 set_property_scheme(repos, scheme)
536 config_set_scheme(repos, scheme, None, mandatory=True)
537 elif scheme is not None:
538 info(scheme_str(scheme))
541 register_command(cmd_svn_branching_scheme)
544 class cmd_svn_set_revprops(Command):
545 """Migrate Bazaar metadata to Subversion revision properties.
547 This requires that you have permission to change the
548 revision properties on the repository.
550 To change these permissions, edit the hooks/pre-revprop-change
551 file in the Subversion repository.
553 takes_args = ['location']
555 def run(self, location="."):
556 raise NotImplementedError(self.run)
559 register_command(cmd_svn_set_revprops)
563 """Returns the testsuite for bzr-svn."""
564 from unittest import TestSuite
565 from bzrlib.plugins.svn import tests
567 suite.addTest(tests.test_suite())
571 if __name__ == '__main__':
572 print ("This is a Bazaar plugin. Copy this directory to ~/.bazaar/plugins "
574 elif __name__ != 'bzrlib.plugins.svn':
575 raise ImportError('The Subversion plugin must be installed as'
576 ' bzrlib.plugins.svn not %s' % __name__)