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 2 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
16 """Checkouts and working trees (working copies)."""
19 from bzrlib import urlutils
20 from bzrlib.branch import PullResult
21 from bzrlib.bzrdir import BzrDirFormat, BzrDir
22 from bzrlib.errors import (InvalidRevisionId, NotBranchError, NoSuchFile,
23 NoRepositoryPresent, BzrError, UninitializableFormat,
25 from bzrlib.inventory import Inventory, InventoryFile, InventoryLink
26 from bzrlib.lockable_files import TransportLock, LockableFiles
27 from bzrlib.lockdir import LockDir
28 from bzrlib.osutils import file_kind, fingerprint_file
29 from bzrlib.revision import NULL_REVISION
30 from bzrlib.trace import mutter
31 from bzrlib.tree import RevisionTree
32 from bzrlib.transport.local import LocalTransport
33 from bzrlib.workingtree import WorkingTree, WorkingTreeFormat
35 from branch import SvnBranch
36 from convert import SvnConverter
37 from errors import LocalCommitsUnsupported, NoSvnRepositoryPresent
38 from mapping import (SVN_PROP_BZR_ANCESTRY, SVN_PROP_BZR_FILEIDS,
39 SVN_PROP_BZR_REVISION_ID, SVN_PROP_BZR_REVISION_INFO)
40 from remote import SvnRemoteAccess
41 from repository import (SvnRepository, revision_id_to_svk_feature,
42 generate_revision_metadata, SVN_PROP_SVK_MERGE)
43 from mapping import escape_svn_path
44 from scheme import BranchingScheme
45 from transport import (SvnRaTransport, bzr_to_svn_url, create_svn_client,
47 from tree import SvnBasisTree
52 import svn.core, svn.wc
53 from svn.core import SubversionException, Pool
55 from errors import NoCheckoutSupport
56 from format import get_rich_root_format
58 class WorkingTreeInconsistent(BzrError):
59 _fmt = """Working copy is in inconsistent state (%(min_revnum)d:%(max_revnum)d)"""
61 def __init__(self, min_revnum, max_revnum):
62 self.min_revnum = min_revnum
63 self.max_revnum = max_revnum
66 class SvnWorkingTree(WorkingTree):
67 """WorkingTree implementation that uses a Subversion Working Copy for storage."""
68 def __init__(self, bzrdir, local_path, branch):
69 self._format = SvnWorkingTreeFormat()
70 self.basedir = local_path
75 self.client_ctx = create_svn_client(self.pool)
76 self.client_ctx.log_msg_func2 = \
77 svn.client.svn_swig_py_get_commit_log_func
80 status = svn.wc.revision_status(self.basedir, None, True, None, None)
81 if status.min_rev != status.max_rev:
82 #raise WorkingTreeInconsistent(status.min_rev, status.max_rev)
83 rev = svn.core.svn_opt_revision_t()
84 rev.kind = svn.core.svn_opt_revision_number
85 rev.value.number = status.max_rev
86 assert status.max_rev == svn.client.update(self.basedir, rev,
87 True, self.client_ctx, Pool())
89 self.base_revnum = status.max_rev
90 self.base_tree = SvnBasisTree(self)
91 self.base_revid = branch.generate_revision_id(self.base_revnum)
93 self.read_working_inventory()
95 self.controldir = os.path.join(self.basedir, svn.wc.get_adm_dir(),
98 os.makedirs(self.controldir)
99 os.makedirs(os.path.join(self.controldir, 'lock'))
102 control_transport = bzrdir.transport.clone(os.path.join(
103 svn.wc.get_adm_dir(), 'bzr'))
104 self._control_files = LockableFiles(control_transport, 'lock', LockDir)
106 def get_ignore_list(self):
107 ignores = set([svn.wc.get_adm_dir()])
108 ignores.update(svn.wc.get_default_ignores(svn_config))
110 def dir_add(wc, prefix):
111 ignorestr = svn.wc.prop_get(svn.core.SVN_PROP_IGNORE,
112 self.abspath(prefix).rstrip("/"), wc)
113 if ignorestr is not None:
114 for pat in ignorestr.splitlines():
115 ignores.add("./"+os.path.join(prefix, pat))
117 entries = svn.wc.entries_read(wc, False)
118 for entry in entries:
122 if entries[entry].kind != svn.core.svn_node_dir:
125 subprefix = os.path.join(prefix, entry)
127 subwc = svn.wc.adm_open3(wc, self.abspath(subprefix), False,
130 dir_add(subwc, subprefix)
132 svn.wc.adm_close(subwc)
142 def is_control_filename(self, path):
143 return svn.wc.is_adm_dir(path)
145 def apply_inventory_delta(self, changes):
146 raise NotImplementedError(self.apply_inventory_delta)
148 def update(self, change_reporter=None):
149 rev = svn.core.svn_opt_revision_t()
150 rev.kind = svn.core.svn_opt_revision_head
151 svn.client.update(self.basedir, rev, True, self.client_ctx)
153 def remove(self, files, verbose=False, to_file=None):
154 # FIXME: Use to_file argument
155 # FIXME: Use verbose argument
156 assert isinstance(files, list)
157 wc = self._get_wc(write_lock=True)
160 svn.wc.delete2(self.abspath(file), wc, None, None, None)
165 self._change_fileid_mapping(None, file)
166 self.read_working_inventory()
168 def _get_wc(self, relpath="", write_lock=False):
169 return svn.wc.adm_open3(None, self.abspath(relpath).rstrip("/"),
172 def _get_rel_wc(self, relpath, write_lock=False):
173 dir = os.path.dirname(relpath)
174 file = os.path.basename(relpath)
175 return (self._get_wc(dir, write_lock), file)
177 def move(self, from_paths, to_dir=None, after=False, **kwargs):
178 # FIXME: Use after argument
180 revt = svn.core.svn_opt_revision_t()
181 revt.kind = svn.core.svn_opt_revision_working
182 for entry in from_paths:
184 to_wc = self._get_wc(to_dir, write_lock=True)
185 svn.wc.copy(self.abspath(entry), to_wc,
186 os.path.basename(entry), None, None)
188 svn.wc.adm_close(to_wc)
190 from_wc = self._get_wc(write_lock=True)
191 svn.wc.delete2(self.abspath(entry), from_wc, None, None, None)
193 svn.wc.adm_close(from_wc)
194 new_name = urlutils.join(to_dir, os.path.basename(entry))
195 self._change_fileid_mapping(self.inventory.path2id(entry), new_name)
196 self._change_fileid_mapping(None, entry)
198 self.read_working_inventory()
200 def rename_one(self, from_rel, to_rel, after=False):
203 revt = svn.core.svn_opt_revision_t()
204 revt.kind = svn.core.svn_opt_revision_unspecified
205 (to_wc, to_file) = self._get_rel_wc(to_rel, write_lock=True)
206 if os.path.dirname(from_rel) == os.path.dirname(to_rel):
207 # Prevent lock contention
210 (from_wc, _) = self._get_rel_wc(from_rel, write_lock=True)
211 from_id = self.inventory.path2id(from_rel)
213 svn.wc.copy(self.abspath(from_rel), to_wc, to_file, None, None)
214 svn.wc.delete2(self.abspath(from_rel), from_wc, None, None, None)
216 svn.wc.adm_close(to_wc)
217 self._change_fileid_mapping(None, from_rel)
218 self._change_fileid_mapping(from_id, to_rel)
219 self.read_working_inventory()
221 def path_to_file_id(self, revnum, current_revnum, path):
222 """Generate a bzr file id from a Subversion file name.
224 :param revnum: Revision number.
225 :param path: Absolute path within the Subversion repository.
226 :return: Tuple with file id and revision id.
228 assert isinstance(revnum, int) and revnum >= 0
229 assert isinstance(path, str)
231 rp = self.branch.unprefix(path)
232 entry = self.base_tree.id_map[rp]
233 assert entry[0] is not None
234 assert isinstance(entry[0], str), "fileid %r for %r is not a string" % (entry[0], path)
237 def read_working_inventory(self):
240 def add_file_to_inv(relpath, id, revid, parent_id):
241 """Add a file to the inventory."""
242 assert isinstance(relpath, unicode)
243 if os.path.islink(self.abspath(relpath)):
244 file = InventoryLink(id, os.path.basename(relpath), parent_id)
245 file.revision = revid
246 file.symlink_target = os.readlink(self.abspath(relpath))
247 file.text_sha1 = None
248 file.text_size = None
249 file.executable = False
252 file = InventoryFile(id, os.path.basename(relpath), parent_id)
253 file.revision = revid
255 data = fingerprint_file(open(self.abspath(relpath)))
256 file.text_sha1 = data['sha1']
257 file.text_size = data['size']
258 file.executable = self.is_executable(id, relpath)
261 # Ignore non-existing files
264 def find_copies(url, relpath=""):
265 wc = self._get_wc(relpath)
266 entries = svn.wc.entries_read(wc, False)
267 for entry in entries.values():
268 subrelpath = os.path.join(relpath, entry.name)
269 if entry.name == "" or entry.kind != 'directory':
270 if ((entry.copyfrom_url == url or entry.url == url) and
271 not (entry.schedule in (svn.wc.schedule_delete,
272 svn.wc.schedule_replace))):
274 self.branch.get_branch_path().strip("/"),
277 find_copies(subrelpath)
280 def find_ids(entry, rootwc):
281 relpath = urllib.unquote(entry.url[len(entry.repos):].strip("/"))
282 assert entry.schedule in (svn.wc.schedule_normal,
283 svn.wc.schedule_delete,
285 svn.wc.schedule_replace)
286 if entry.schedule == svn.wc.schedule_normal:
287 assert entry.revision >= 0
289 return self.path_to_file_id(entry.cmt_rev, entry.revision,
291 elif entry.schedule == svn.wc.schedule_delete:
293 elif (entry.schedule == svn.wc.schedule_add or
294 entry.schedule == svn.wc.schedule_replace):
295 # See if the file this file was copied from disappeared
296 # and has no other copies -> in that case, take id of other file
297 if (entry.copyfrom_url and
298 list(find_copies(entry.copyfrom_url)) == [relpath]):
299 return self.path_to_file_id(entry.copyfrom_rev,
300 entry.revision, entry.copyfrom_url[len(entry.repos):])
301 ids = self._get_new_file_ids(rootwc)
302 if ids.has_key(relpath):
303 return (ids[relpath], None)
304 # FIXME: Generate more random file ids
305 return ("NEW-" + escape_svn_path(entry.url[len(entry.repos):].strip("/")), None)
307 def add_dir_to_inv(relpath, wc, parent_id):
308 assert isinstance(relpath, unicode)
309 entries = svn.wc.entries_read(wc, False)
311 assert parent_id is None or isinstance(parent_id, str), \
312 "%r is not a string" % parent_id
313 (id, revid) = find_ids(entry, rootwc)
315 mutter('no id for %r' % entry.url)
317 assert revid is None or isinstance(revid, str), "%r is not a string" % revid
318 assert isinstance(id, str), "%r is not a string" % id
320 # First handle directory itself
321 inv.add_path(relpath.decode("utf-8"), 'directory', id, parent_id).revision = revid
323 inv.revision_id = revid
329 subrelpath = os.path.join(relpath, name.decode("utf-8"))
331 entry = entries[name]
334 if entry.kind == svn.core.svn_node_dir:
335 subwc = svn.wc.adm_open3(wc, self.abspath(subrelpath),
338 add_dir_to_inv(subrelpath, subwc, id)
340 svn.wc.adm_close(subwc)
342 (subid, subrevid) = find_ids(entry, rootwc)
344 add_file_to_inv(subrelpath, subid, subrevid, id)
346 mutter('no id for %r' % entry.url)
348 rootwc = self._get_wc()
350 add_dir_to_inv(u"", rootwc, None)
352 svn.wc.adm_close(rootwc)
354 self._set_inventory(inv, dirty=False)
357 def set_last_revision(self, revid):
358 mutter('setting last revision to %r' % revid)
359 if revid is None or revid == NULL_REVISION:
360 self.base_revid = revid
362 self.base_tree = RevisionTree(self, Inventory(), revid)
365 rev = self.branch.lookup_revision_id(revid)
366 self.base_revnum = rev
367 self.base_revid = revid
368 self.base_tree = SvnBasisTree(self)
370 # TODO: Implement more efficient version
371 newrev = self.branch.repository.get_revision(revid)
372 newrevtree = self.branch.repository.revision_tree(revid)
374 def update_settings(wc, path):
375 id = newrevtree.inventory.path2id(path)
376 mutter("Updating settings for %r" % id)
377 revnum = self.branch.lookup_revision_id(
378 newrevtree.inventory[id].revision)
380 svn.wc.process_committed2(self.abspath(path).rstrip("/"), wc,
382 svn.core.svn_time_to_cstring(newrev.timestamp),
383 newrev.committer, None, False)
385 if newrevtree.inventory[id].kind != 'directory':
388 entries = svn.wc.entries_read(wc, True)
389 for entry in entries:
393 subwc = svn.wc.adm_open3(wc, os.path.join(self.basedir, path, entry), False, 0, None)
395 update_settings(subwc, os.path.join(path, entry))
397 svn.wc.adm_close(subwc)
399 # Set proper version for all files in the wc
400 wc = self._get_wc(write_lock=True)
402 update_settings(wc, "")
405 self.base_revid = revid
407 def commit(self, message=None, message_callback=None, revprops=None,
408 timestamp=None, timezone=None, committer=None, rev_id=None,
409 allow_pointless=True, strict=False, verbose=False, local=False,
410 reporter=None, config=None, specific_files=None, author=None):
411 if author is not None:
412 revprops['author'] = author
413 # FIXME: Use allow_pointless
415 # FIXME: Use reporter
418 raise LocalCommitsUnsupported()
421 specific_files = [self.abspath(x).encode('utf8') for x in specific_files]
423 specific_files = [self.basedir.encode('utf8')]
425 if message_callback is not None:
426 def log_message_func(items, pool):
427 """ Simple log message provider for unit tests. """
428 return message_callback(self).encode("utf-8")
430 assert isinstance(message, basestring)
431 def log_message_func(items, pool):
432 """ Simple log message provider for unit tests. """
433 return message.encode("utf-8")
435 self.client_ctx.log_msg_baton2 = log_message_func
436 if rev_id is not None:
437 extra = "%d %s\n" % (self.branch.revno()+1, rev_id)
440 wc = self._get_wc(write_lock=True)
442 svn.wc.prop_set(SVN_PROP_BZR_REVISION_ID+str(self.branch.scheme),
443 self._get_bzr_revids() + extra,
445 svn.wc.prop_set(SVN_PROP_BZR_REVISION_INFO,
446 generate_revision_metadata(timestamp,
456 commit_info = svn.client.commit3(specific_files, True, False,
458 except SubversionException, (_, num):
459 if num == svn.core.SVN_ERR_FS_TXN_OUT_OF_DATE:
460 raise OutOfDateTree(self)
463 # Reset properties so the next subversion commit won't
464 # accidently set these properties.
465 wc = self._get_wc(write_lock=True)
466 svn.wc.prop_set(SVN_PROP_BZR_REVISION_ID+str(self.branch.scheme),
467 self._get_bzr_revids(), self.basedir, wc)
468 svn.wc.prop_set(SVN_PROP_BZR_REVISION_INFO,
469 self.branch.repository.branchprop_list.get_property(
470 self.branch.get_branch_path(self.base_revnum),
472 SVN_PROP_BZR_REVISION_INFO, ""),
477 self.client_ctx.log_msg_baton2 = None
479 revid = self.branch.generate_revision_id(commit_info.revision)
481 self.base_revid = revid
482 self.base_revnum = commit_info.revision
483 self.base_tree = SvnBasisTree(self)
487 def smart_add(self, file_list, recurse=True, action=None, save=True):
488 assert isinstance(recurse, bool)
490 action = bzrlib.add.AddAction()
493 # no paths supplied: add the entire tree.
498 for file_path in file_list:
500 file_path = os.path.abspath(file_path)
501 f = self.relpath(file_path)
502 wc = self._get_wc(os.path.dirname(f), write_lock=True)
504 if not self.inventory.has_filename(f):
506 mutter('adding %r' % file_path)
507 svn.wc.add2(file_path, wc, None, 0, None, None, None)
508 added.append(file_path)
509 if recurse and file_kind(file_path) == 'directory':
510 # Filter out ignored files and update ignored
511 for c in os.listdir(file_path):
512 if self.is_control_filename(c):
514 c_path = os.path.join(file_path, c)
515 ignore_glob = self.is_ignored(c)
516 if ignore_glob is not None:
517 ignored.setdefault(ignore_glob, []).append(c_path)
522 cadded, cignored = self.smart_add(todo, recurse, action, save)
524 ignored.update(cignored)
525 return added, ignored
527 def add(self, files, ids=None, kinds=None):
529 if isinstance(files, str):
531 if isinstance(ids, str):
535 assert isinstance(files, list)
537 wc = self._get_wc(os.path.dirname(f), write_lock=True)
540 svn.wc.add2(os.path.join(self.basedir, f), wc, None, 0,
543 self._change_fileid_mapping(ids.next(), f, wc)
544 except SubversionException, (_, num):
545 if num == svn.core.SVN_ERR_ENTRY_EXISTS:
547 elif num == svn.core.SVN_ERR_WC_PATH_NOT_FOUND:
548 raise NoSuchFile(path=f)
552 self.read_working_inventory()
554 def basis_tree(self):
555 if self.base_revid is None or self.base_revid == NULL_REVISION:
556 return self.branch.repository.revision_tree(self.base_revid)
558 return self.base_tree
560 def pull(self, source, overwrite=False, stop_revision=None,
561 delta_reporter=None, possible_transports=None):
562 # FIXME: Use delta_reporter
563 # FIXME: Use overwrite
564 result = PullResult()
565 result.source_branch = source
566 result.master_branch = None
567 result.target_branch = self.branch
568 (result.old_revno, result.old_revid) = self.branch.last_revision_info()
569 if stop_revision is None:
570 stop_revision = self.branch.last_revision()
571 rev = svn.core.svn_opt_revision_t()
572 rev.kind = svn.core.svn_opt_revision_number
573 rev.value.number = self.branch.lookup_revision_id(stop_revision)
574 fetched = svn.client.update(self.basedir, rev, True, self.client_ctx)
575 self.base_revid = self.branch.generate_revision_id(fetched)
576 result.new_revid = self.base_revid
577 result.new_revno = self.branch.revision_id_to_revno(result.new_revid)
580 def get_file_sha1(self, file_id, path=None, stat_value=None):
582 path = self._inventory.id2path(file_id)
583 return fingerprint_file(open(self.abspath(path)))['sha1']
585 def _change_fileid_mapping(self, id, path, wc=None):
587 subwc = self._get_wc(write_lock=True)
590 new_entries = self._get_new_file_ids(subwc)
592 if new_entries.has_key(path):
593 del new_entries[path]
595 assert isinstance(id, str)
596 new_entries[path] = id
597 existing = "".join(map(lambda (path, id): "%s\t%s\n" % (path, id), new_entries.items()))
599 svn.wc.prop_set(SVN_PROP_BZR_FILEIDS, existing.encode("utf-8"), self.basedir, subwc)
601 svn.wc.adm_close(subwc)
603 def _get_new_file_ids(self, wc):
604 committed = self.branch.repository.branchprop_list.get_property(
605 self.branch.get_branch_path(self.base_revnum), self.base_revnum,
606 SVN_PROP_BZR_FILEIDS, "")
607 existing = svn.wc.prop_get(SVN_PROP_BZR_FILEIDS, self.basedir, wc)
608 if existing is None or committed == existing:
610 return dict(map(lambda x: str(x).split("\t"),
611 existing.splitlines()))
613 def _get_bzr_revids(self):
614 return self.branch.repository.branchprop_list.get_property(
615 self.branch.get_branch_path(self.base_revnum),
617 SVN_PROP_BZR_REVISION_ID+str(self.branch.scheme), "")
619 def _get_bzr_merges(self):
620 return self.branch.repository.branchprop_list.get_property(
621 self.branch.get_branch_path(self.base_revnum),
622 self.base_revnum, SVN_PROP_BZR_ANCESTRY+str(self.branch.scheme), "")
624 def _get_svk_merges(self):
625 return self.branch.repository.branchprop_list.get_property(
626 self.branch.get_branch_path(self.base_revnum),
627 self.base_revnum, SVN_PROP_SVK_MERGE, "")
629 def set_pending_merges(self, merges):
630 """See MutableTree.set_pending_merges()."""
631 wc = self._get_wc(write_lock=True)
635 bzr_merge = "\t".join(merges) + "\n"
639 svn.wc.prop_set(SVN_PROP_BZR_ANCESTRY+str(self.branch.scheme),
640 self._get_bzr_merges() + bzr_merge,
647 svk_merge += revision_id_to_svk_feature(merge) + "\n"
648 except InvalidRevisionId:
651 svn.wc.prop_set2(SVN_PROP_SVK_MERGE,
652 self._get_svk_merges() + svk_merge, self.basedir,
657 def add_pending_merge(self, revid):
658 merges = self.pending_merges()
660 self.set_pending_merges(merges)
662 def pending_merges(self):
663 merged = self._get_bzr_merges().splitlines()
666 merged_data = svn.wc.prop_get(
667 SVN_PROP_BZR_ANCESTRY+str(self.branch.scheme), self.basedir, wc)
668 if merged_data is None:
671 set_merged = merged_data.splitlines()
675 assert (len(merged) == len(set_merged) or
676 len(merged)+1 == len(set_merged))
678 if len(set_merged) > len(merged):
679 return set_merged[-1].split("\t")
683 def _reset_data(self):
687 # non-implementation specific cleanup
690 # reverse order of locking.
692 return self._control_files.unlock()
698 class SvnWorkingTreeFormat(WorkingTreeFormat):
699 """Subversion working copy format."""
700 def __get_matchingbzrdir(self):
701 return SvnWorkingTreeDirFormat()
703 _matchingbzrdir = property(__get_matchingbzrdir)
705 def get_format_description(self):
706 return "Subversion Working Copy"
708 def get_format_string(self):
709 return "Subversion Working Copy Format"
711 def initialize(self, a_bzrdir, revision_id=None):
712 raise NotImplementedError(self.initialize)
714 def open(self, a_bzrdir):
715 raise NotImplementedError(self.initialize)
718 class SvnCheckout(BzrDir):
719 """BzrDir implementation for Subversion checkouts (directories
720 containing a .svn subdirectory."""
721 def __init__(self, transport, format):
722 super(SvnCheckout, self).__init__(transport, format)
723 self.local_path = transport.local_abspath(".")
725 # Open related remote repository + branch
726 wc = svn.wc.adm_open3(None, self.local_path, False, 0, None)
728 svn_url = svn.wc.entry(self.local_path, wc, True).url
732 remote_transport = SvnRaTransport(svn_url)
733 self.remote_bzrdir = SvnRemoteAccess(remote_transport)
734 self.svn_root_transport = remote_transport.clone_root()
735 self.root_transport = self.transport = transport
737 def clone(self, path, revision_id=None, force_new_repo=False):
738 raise NotImplementedError(self.clone)
740 def open_workingtree(self, _unsupported=False, recommend_upgrade=False):
741 return SvnWorkingTree(self, self.local_path, self.open_branch())
743 def sprout(self, url, revision_id=None, force_new_repo=False,
744 recurse='down', possible_transports=None, accelerator_tree=None):
745 # FIXME: honor force_new_repo
747 result = get_rich_root_format().initialize(url)
748 repo = self.find_repository()
749 repo.clone(result, revision_id)
750 branch = self.open_branch()
751 branch.sprout(result, revision_id)
752 result.create_workingtree()
755 def open_repository(self):
756 raise NoRepositoryPresent(self)
758 def find_repository(self):
759 return SvnRepository(self, self.svn_root_transport, self.remote_bzrdir.branch_path)
761 def needs_format_conversion(self, format=None):
763 format = BzrDirFormat.get_default_format()
764 return not isinstance(self._format, format.__class__)
766 def create_workingtree(self, revision_id=None):
767 """See BzrDir.create_workingtree().
769 Not implemented for Subversion because having a .svn directory
770 implies having a working copy.
772 raise NotImplementedError(self.create_workingtree)
774 def create_branch(self):
775 """See BzrDir.create_branch()."""
776 raise NotImplementedError(self.create_branch)
778 def open_branch(self, unsupported=True):
779 """See BzrDir.open_branch()."""
780 repos = self.find_repository()
783 branch = SvnBranch(self.svn_root_transport.base, repos,
784 self.remote_bzrdir.branch_path)
785 except SubversionException, (_, num):
786 if num == svn.core.SVN_ERR_WC_NOT_DIRECTORY:
787 raise NotBranchError(path=self.base)
790 branch.bzrdir = self.remote_bzrdir