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
16 """Checkouts and working trees (working copies)."""
18 import bzrlib, bzrlib.add
19 from bzrlib import osutils, urlutils
20 from bzrlib.branch import PullResult
21 from bzrlib.bzrdir import BzrDirFormat, BzrDir
22 from bzrlib.errors import (InvalidRevisionId, NotBranchError, NoSuchFile,
24 OutOfDateTree, NoWorkingTree, UnsupportedFormatError)
25 from bzrlib.inventory import Inventory, InventoryFile, InventoryLink
26 from bzrlib.lockable_files import LockableFiles
27 from bzrlib.lockdir import LockDir
28 from bzrlib.revision import NULL_REVISION
29 from bzrlib.trace import mutter
30 from bzrlib.transport import get_transport
31 from bzrlib.workingtree import WorkingTree, WorkingTreeFormat
33 from bzrlib.plugins.svn import core, properties
34 from bzrlib.plugins.svn.auth import create_auth_baton
35 from bzrlib.plugins.svn.branch import SvnBranch
36 from bzrlib.plugins.svn.commit import _revision_id_to_svk_feature
37 from bzrlib.plugins.svn.core import SubversionException
38 from bzrlib.plugins.svn.errors import ERR_FS_TXN_OUT_OF_DATE, ERR_ENTRY_EXISTS, ERR_WC_PATH_NOT_FOUND, ERR_WC_NOT_DIRECTORY, NotSvnBranchPath
39 from bzrlib.plugins.svn.format import get_rich_root_format
40 from bzrlib.plugins.svn.mapping import escape_svn_path
41 from bzrlib.plugins.svn.remote import SvnRemoteAccess
42 from bzrlib.plugins.svn.repository import SvnRepository
43 from bzrlib.plugins.svn.svk import SVN_PROP_SVK_MERGE, parse_svk_features, serialize_svk_features
44 from bzrlib.plugins.svn.transport import (SvnRaTransport, bzr_to_svn_url,
46 from bzrlib.plugins.svn.tree import SvnBasisTree
47 from bzrlib.plugins.svn.wc import *
52 def update_wc(adm, basedir, conn, revnum):
53 # FIXME: honor SVN_CONFIG_SECTION_HELPERS:SVN_CONFIG_OPTION_DIFF3_CMD
54 # FIXME: honor SVN_CONFIG_SECTION_MISCELLANY:SVN_CONFIG_OPTION_USE_COMMIT_TIMES
55 # FIXME: honor SVN_CONFIG_SECTION_MISCELLANY:SVN_CONFIG_OPTION_PRESERVED_CF_EXTS
56 editor = adm.get_update_editor(basedir, False, True)
57 assert editor is not None
58 reporter = conn.do_update(revnum, "", True, editor)
59 adm.crawl_revisions(basedir, reporter, restore_files=False,
60 recurse=True, use_commit_times=False)
61 # FIXME: handle externals
64 def generate_ignore_list(ignore_map):
65 """Create a list of ignores, ordered by directory.
67 :param ignore_map: Dictionary with paths as keys, patterns as values.
68 :return: list of ignores
71 keys = ignore_map.keys()
72 for k in sorted(keys):
74 if k.strip("/") != "":
75 elements.append(k.strip("/"))
76 elements.append(ignore_map[k].strip("/"))
77 ignores.append("/".join(elements))
81 class SvnWorkingTree(WorkingTree):
82 """WorkingTree implementation that uses a Subversion Working Copy for storage."""
83 def __init__(self, bzrdir, local_path, branch):
84 version = check_wc(local_path)
85 self._format = SvnWorkingTreeFormat(version)
86 self.basedir = local_path
87 assert isinstance(self.basedir, unicode)
93 max_rev = revision_status(self.basedir, None, True)[1]
94 self.base_revnum = max_rev
95 self.base_revid = branch.generate_revision_id(self.base_revnum)
96 self.base_tree = SvnBasisTree(self)
98 self.read_working_inventory()
100 self._detect_case_handling()
101 self._transport = bzrdir.get_workingtree_transport(None)
102 self.controldir = os.path.join(bzrdir.svn_controldir, 'bzr')
104 os.makedirs(self.controldir)
105 os.makedirs(os.path.join(self.controldir, 'lock'))
108 control_transport = bzrdir.transport.clone(urlutils.join(
109 get_adm_dir(), 'bzr'))
110 self._control_files = LockableFiles(control_transport, 'lock', LockDir)
112 def get_ignore_list(self):
113 ignores = set([get_adm_dir()])
114 ignores.update(svn_config.get_default_ignores())
116 def dir_add(wc, prefix, patprefix):
117 ignorestr = wc.prop_get(properties.PROP_IGNORE,
118 self.abspath(prefix).rstrip("/"))
119 if ignorestr is not None:
120 for pat in ignorestr.splitlines():
121 ignores.add(urlutils.joinpath(patprefix, pat))
123 entries = wc.entries_read(False)
124 for entry in entries:
128 # Ignore ignores on things that aren't directories
129 if entries[entry].kind != core.NODE_DIR:
132 subprefix = os.path.join(prefix, entry)
134 subwc = WorkingCopy(wc, self.abspath(subprefix))
136 dir_add(subwc, subprefix, urlutils.joinpath(patprefix, entry))
148 def is_control_filename(self, path):
149 return is_adm_dir(path)
151 def apply_inventory_delta(self, changes):
152 raise NotImplementedError(self.apply_inventory_delta)
154 def _update(self, revnum=None):
156 # FIXME: should be able to use -1 here
157 revnum = self.branch.get_revnum()
158 adm = self._get_wc(write_lock=True)
160 conn = self.branch.repository.transport.get_connection(self.branch.get_branch_path())
162 update_wc(adm, self.basedir, conn, revnum)
164 self.branch.repository.transport.add_connection(conn)
169 def update(self, change_reporter=None, possible_transports=None, revnum=None):
170 orig_revnum = self.base_revnum
171 self.base_revnum = self._update(revnum)
172 self.base_revid = self.branch.generate_revision_id(self.base_revnum)
173 self.base_tree = None
174 self.read_working_inventory()
175 return self.base_revnum - orig_revnum
177 def remove(self, files, verbose=False, to_file=None, keep_files=True,
179 # FIXME: Use to_file argument
180 # FIXME: Use verbose argument
181 assert isinstance(files, list)
182 wc = self._get_wc(write_lock=True)
185 wc.delete(self.abspath(file))
190 self._change_fileid_mapping(None, file)
191 self.read_working_inventory()
193 def _get_wc(self, relpath="", write_lock=False):
194 return WorkingCopy(None, self.abspath(relpath).rstrip("/"),
197 def _get_rel_wc(self, relpath, write_lock=False):
198 dir = os.path.dirname(relpath)
199 file = os.path.basename(relpath)
200 return (self._get_wc(dir, write_lock), file)
202 def move(self, from_paths, to_dir=None, after=False, **kwargs):
203 # FIXME: Use after argument
205 for entry in from_paths:
207 to_wc = self._get_wc(to_dir, write_lock=True)
208 to_wc.copy(self.abspath(entry), os.path.basename(entry))
212 from_wc = self._get_wc(write_lock=True)
213 from_wc.delete(self.abspath(entry))
216 new_name = urlutils.join(to_dir, os.path.basename(entry))
217 self._change_fileid_mapping(self.inventory.path2id(entry), new_name)
218 self._change_fileid_mapping(None, entry)
220 self.read_working_inventory()
222 def rename_one(self, from_rel, to_rel, after=False):
225 (to_wc, to_file) = self._get_rel_wc(to_rel, write_lock=True)
226 if os.path.dirname(from_rel) == os.path.dirname(to_rel):
227 # Prevent lock contention
230 (from_wc, _) = self._get_rel_wc(from_rel, write_lock=True)
231 from_id = self.inventory.path2id(from_rel)
233 to_wc.copy(self.abspath(from_rel), to_file)
234 from_wc.delete(self.abspath(from_rel))
237 self._change_fileid_mapping(None, from_rel)
238 self._change_fileid_mapping(from_id, to_rel)
239 self.read_working_inventory()
241 def path_to_file_id(self, revnum, current_revnum, path):
242 """Generate a bzr file id from a Subversion file name.
244 :param revnum: Revision number.
245 :param path: Absolute path within the Subversion repository.
246 :return: Tuple with file id and revision id.
248 assert isinstance(revnum, int) and revnum >= 0
249 assert isinstance(path, str)
251 rp = self.branch.unprefix(path)
252 entry = self.basis_tree().id_map[rp.decode("utf-8")]
253 assert entry[0] is not None
254 assert isinstance(entry[0], str), "fileid %r for %r is not a string" % (entry[0], path)
257 def read_working_inventory(self):
260 def add_file_to_inv(relpath, id, revid, parent_id):
261 """Add a file to the inventory."""
262 assert isinstance(relpath, unicode)
263 if os.path.islink(self.abspath(relpath)):
264 file = InventoryLink(id, os.path.basename(relpath), parent_id)
265 file.revision = revid
266 file.symlink_target = os.readlink(self.abspath(relpath))
267 file.text_sha1 = None
268 file.text_size = None
269 file.executable = False
272 file = InventoryFile(id, os.path.basename(relpath), parent_id)
273 file.revision = revid
275 data = osutils.fingerprint_file(open(self.abspath(relpath)))
276 file.text_sha1 = data['sha1']
277 file.text_size = data['size']
278 file.executable = self.is_executable(id, relpath)
281 # Ignore non-existing files
284 def find_copies(url, relpath=""):
285 wc = self._get_wc(relpath)
286 entries = wc.entries_read(False)
287 for entry in entries.values():
288 subrelpath = os.path.join(relpath, entry.name)
289 if entry.name == "" or entry.kind != 'directory':
290 if ((entry.copyfrom_url == url or entry.url == url) and
291 not (entry.schedule in (SCHEDULE_DELETE,
294 self.branch.get_branch_path().strip("/"),
297 find_copies(subrelpath)
300 def find_ids(entry, rootwc):
301 relpath = urllib.unquote(entry.url[len(entry.repos):].strip("/"))
302 assert entry.schedule in (SCHEDULE_NORMAL,
306 if entry.schedule == SCHEDULE_NORMAL:
307 assert entry.revision >= 0
309 return self.path_to_file_id(entry.cmt_rev, entry.revision,
311 elif entry.schedule == SCHEDULE_DELETE:
313 elif (entry.schedule == SCHEDULE_ADD or
314 entry.schedule == SCHEDULE_REPLACE):
315 # See if the file this file was copied from disappeared
316 # and has no other copies -> in that case, take id of other file
317 if (entry.copyfrom_url and
318 list(find_copies(entry.copyfrom_url)) == [relpath]):
319 return self.path_to_file_id(entry.copyfrom_rev,
320 entry.revision, entry.copyfrom_url[len(entry.repos):])
321 ids = self._get_new_file_ids(rootwc)
322 if ids.has_key(relpath):
323 return (ids[relpath], None)
324 # FIXME: Generate more random file ids
325 return ("NEW-" + escape_svn_path(entry.url[len(entry.repos):].strip("/")), None)
327 def add_dir_to_inv(relpath, wc, parent_id):
328 assert isinstance(relpath, unicode)
329 entries = wc.entries_read(False)
331 assert parent_id is None or isinstance(parent_id, str), \
332 "%r is not a string" % parent_id
333 (id, revid) = find_ids(entry, rootwc)
335 mutter('no id for %r', entry.url)
337 assert revid is None or isinstance(revid, str), "%r is not a string" % revid
338 assert isinstance(id, str), "%r is not a string" % id
340 # First handle directory itself
341 inv.add_path(relpath.decode("utf-8"), 'directory', id, parent_id).revision = revid
343 inv.revision_id = revid
349 subrelpath = os.path.join(relpath, name.decode("utf-8"))
351 entry = entries[name]
354 if entry.kind == core.NODE_DIR:
355 subwc = WorkingCopy(wc, self.abspath(subrelpath))
357 assert isinstance(subrelpath, unicode)
358 add_dir_to_inv(subrelpath, subwc, id)
362 (subid, subrevid) = find_ids(entry, rootwc)
364 assert isinstance(subrelpath, unicode)
365 add_file_to_inv(subrelpath, subid, subrevid, id)
367 mutter('no id for %r', entry.url)
369 rootwc = self._get_wc()
371 add_dir_to_inv(u"", rootwc, None)
375 self._set_inventory(inv, dirty=False)
378 def set_last_revision(self, revid):
379 mutter('setting last revision to %r', revid)
380 if revid is None or revid == NULL_REVISION:
381 self.base_revid = revid
383 self.base_tree = None
386 rev = self.branch.lookup_revision_id(revid)
387 self.base_revnum = rev
388 self.base_revid = revid
389 self.base_tree = None
391 def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
392 """See MutableTree.set_parent_trees."""
393 self.set_parent_ids([rev for (rev, tree) in parents_list])
395 def set_parent_ids(self, parent_ids):
396 super(SvnWorkingTree, self).set_parent_ids(parent_ids)
397 if parent_ids == [] or parent_ids[0] == NULL_REVISION:
400 merges = parent_ids[1:]
401 adm = self._get_wc(write_lock=True)
403 svk_merges = parse_svk_features(self._get_svk_merges(self._get_base_branch_props()))
408 svk_merges.add(_revision_id_to_svk_feature(merge))
409 except InvalidRevisionId:
412 adm.prop_set(SVN_PROP_SVK_MERGE,
413 serialize_svk_features(svk_merges), self.basedir)
417 def smart_add(self, file_list, recurse=True, action=None, save=True):
418 assert isinstance(recurse, bool)
420 action = bzrlib.add.AddAction()
423 # no paths supplied: add the entire tree.
428 for file_path in file_list:
430 file_path = os.path.abspath(file_path)
431 f = self.relpath(file_path)
432 wc = self._get_wc(os.path.dirname(f), write_lock=True)
434 if not self.inventory.has_filename(f):
436 mutter('adding %r', file_path)
438 added.append(file_path)
439 if recurse and osutils.file_kind(file_path) == 'directory':
440 # Filter out ignored files and update ignored
441 for c in os.listdir(file_path):
442 if self.is_control_filename(c):
444 c_path = os.path.join(file_path, c)
445 ignore_glob = self.is_ignored(c)
446 if ignore_glob is not None:
447 ignored.setdefault(ignore_glob, []).append(c_path)
452 cadded, cignored = self.smart_add(todo, recurse, action, save)
454 ignored.update(cignored)
455 return added, ignored
457 def add(self, files, ids=None, kinds=None):
459 if isinstance(files, str):
461 if isinstance(ids, str):
465 assert isinstance(files, list)
467 wc = self._get_wc(os.path.dirname(f), write_lock=True)
470 wc.add(self.abspath(f))
472 self._change_fileid_mapping(ids.next(), f, wc)
473 except SubversionException, (_, num):
474 if num == ERR_ENTRY_EXISTS:
476 elif num == ERR_WC_PATH_NOT_FOUND:
477 raise NoSuchFile(path=f)
481 self.read_working_inventory()
483 def basis_tree(self):
484 if self.base_revid is None or self.base_revid == NULL_REVISION:
485 return self.branch.repository.revision_tree(self.base_revid)
487 if self.base_tree is None:
488 self.base_tree = SvnBasisTree(self)
490 return self.base_tree
492 def pull(self, source, overwrite=False, stop_revision=None,
493 delta_reporter=None, possible_transports=None):
494 # FIXME: Use delta_reporter
496 # FIXME: Use overwrite
497 result = self.branch.pull(source, overwrite=overwrite, stop_revision=stop_revision)
498 fetched = self._update(self.branch.get_revnum())
499 self.base_revnum = fetched
500 self.base_revid = self.branch.generate_revision_id(fetched)
501 self.base_tree = None
502 self.read_working_inventory()
505 def get_file_sha1(self, file_id, path=None, stat_value=None):
507 path = self._inventory.id2path(file_id)
508 return osutils.fingerprint_file(open(self.abspath(path)))['sha1']
510 def _change_fileid_mapping(self, id, path, wc=None):
512 subwc = self._get_wc(write_lock=True)
515 new_entries = self._get_new_file_ids(subwc)
517 if new_entries.has_key(path):
518 del new_entries[path]
520 assert isinstance(id, str)
521 new_entries[path] = id
522 fileprops = self._get_branch_props()
523 self.branch.mapping.export_fileid_map(new_entries, None, fileprops)
524 self._set_branch_props(subwc, fileprops)
528 def _get_branch_props(self):
531 (prop_changes, orig_props) = wc.get_prop_diffs(self.basedir)
532 for k,v in prop_changes:
541 def _set_branch_props(self, wc, fileprops):
542 for k,v in fileprops.items():
543 wc.prop_set(k, v, self.basedir)
545 def _get_base_branch_props(self):
548 (prop_changes, orig_props) = wc.get_prop_diffs(self.basedir)
553 def _get_new_file_ids(self, wc):
554 return self.branch.mapping.import_fileid_map({},
555 self._get_branch_props())
557 def _get_svk_merges(self, base_branch_props):
558 return base_branch_props.get(SVN_PROP_SVK_MERGE, "")
560 def apply_inventory_delta(self, delta):
563 def _last_revision(self):
564 return self.base_revid
566 def path_content_summary(self, path, _lstat=os.lstat,
567 _mapper=osutils.file_kind_from_stat_mode):
568 """See Tree.path_content_summary."""
569 abspath = self.abspath(path)
571 stat_result = _lstat(abspath)
573 if getattr(e, 'errno', None) == errno.ENOENT:
575 return ('missing', None, None, None)
576 # propagate other errors
578 kind = _mapper(stat_result.st_mode)
580 size = stat_result.st_size
581 # try for a stat cache lookup
582 executable = self._is_executable_from_path_and_stat(path, stat_result)
583 return (kind, size, executable, self._sha_from_stat(
585 elif kind == 'directory':
586 return kind, None, None, None
587 elif kind == 'symlink':
588 return ('symlink', None, None, os.readlink(abspath))
590 return (kind, None, None, None)
592 def _get_base_revmeta(self):
593 return self.branch.repository._revmeta_provider.get_revision(self.branch.get_branch_path(self.base_revnum), self.base_revnum)
595 def _reset_data(self):
599 # non-implementation specific cleanup
602 # reverse order of locking.
604 return self._control_files.unlock()
608 if not osutils.supports_executable():
609 def is_executable(self, file_id, path=None):
610 inv = self.basis_tree()._inventory
612 return inv[file_id].executable
613 # Default to not executable
616 def update_basis_by_delta(self, new_revid, delta):
617 """Update the parents of this tree after a commit.
619 This gives the tree one parent, with revision id new_revid. The
620 inventory delta is applied to the current basis tree to generate the
621 inventory for the parent new_revid, and all other parent trees are
624 All the changes in the delta should be changes synchronising the basis
625 tree with some or all of the working tree, with a change to a directory
626 requiring that its contents have been recursively included. That is,
627 this is not a general purpose tree modification routine, but a helper
628 for commit which is not required to handle situations that do not arise
631 :param new_revid: The new revision id for the trees parent.
632 :param delta: An inventory delta (see apply_inventory_delta) describing
633 the changes from the current left most parent revision to new_revid.
635 rev = self.branch.lookup_revision_id(new_revid)
636 self.base_revnum = rev
637 self.base_revid = new_revid
638 self.base_tree = None
640 # TODO: Implement more efficient version
641 newrev = self.branch.repository.get_revision(new_revid)
642 newrevtree = self.branch.repository.revision_tree(new_revid)
643 svn_revprops = self.branch.repository._log.revprop_list(rev)
645 def update_settings(wc, path):
646 id = newrevtree.inventory.path2id(path)
647 mutter("Updating settings for %r", id)
648 revnum = self.branch.lookup_revision_id(
649 newrevtree.inventory[id].revision)
651 if newrevtree.inventory[id].kind != 'directory':
654 entries = wc.entries_read(True)
655 for name, entry in entries.items():
659 wc.process_committed(self.abspath(path).rstrip("/"),
660 False, self.branch.lookup_revision_id(newrevtree.inventory[id].revision),
661 svn_revprops[properties.PROP_REVISION_DATE],
662 svn_revprops.get(properties.PROP_REVISION_AUTHOR, ""))
664 child_path = os.path.join(path, name.decode("utf-8"))
666 fileid = newrevtree.inventory.path2id(child_path)
668 if newrevtree.inventory[fileid].kind == 'directory':
669 subwc = WorkingCopy(wc, self.abspath(child_path).rstrip("/"), write_lock=True)
671 update_settings(subwc, child_path)
675 # Set proper version for all files in the wc
676 wc = self._get_wc(write_lock=True)
678 wc.process_committed(self.basedir,
679 False, self.branch.lookup_revision_id(newrevtree.inventory.root.revision),
680 svn_revprops[properties.PROP_REVISION_DATE],
681 svn_revprops.get(properties.PROP_REVISION_AUTHOR, ""))
682 update_settings(wc, "")
686 self.set_parent_ids([new_revid])
689 class SvnWorkingTreeFormat(WorkingTreeFormat):
690 """Subversion working copy format."""
691 def __init__(self, version):
692 self.version = version
694 def __get_matchingbzrdir(self):
695 return SvnWorkingTreeDirFormat()
697 _matchingbzrdir = property(__get_matchingbzrdir)
699 def get_format_description(self):
700 return "Subversion Working Copy Version %d" % self.version
702 def get_format_string(self):
703 raise NotImplementedError
705 def initialize(self, a_bzrdir, revision_id=None):
706 raise NotImplementedError(self.initialize)
708 def open(self, a_bzrdir):
709 raise NotImplementedError(self.initialize)
712 class SvnCheckout(BzrDir):
713 """BzrDir implementation for Subversion checkouts (directories
714 containing a .svn subdirectory."""
715 def __init__(self, transport, format):
716 super(SvnCheckout, self).__init__(transport, format)
717 self.local_path = transport.local_abspath(".")
719 # Open related remote repository + branch
721 wc = WorkingCopy(None, self.local_path)
722 except SubversionException, (msg, ERR_WC_UNSUPPORTED_FORMAT):
723 raise UnsupportedFormatError(msg, kind='workingtree')
725 self.svn_url = wc.entry(self.local_path, True).url
729 self._remote_transport = None
730 self._remote_bzrdir = None
731 self.svn_controldir = os.path.join(self.local_path, get_adm_dir())
732 self.root_transport = self.transport = transport
734 def get_remote_bzrdir(self):
735 if self._remote_bzrdir is None:
736 self._remote_bzrdir = SvnRemoteAccess(self.get_remote_transport())
737 return self._remote_bzrdir
739 def get_remote_transport(self):
740 if self._remote_transport is None:
741 self._remote_transport = SvnRaTransport(self.svn_url)
742 return self._remote_transport
744 def clone(self, path, revision_id=None, force_new_repo=False):
745 raise NotImplementedError(self.clone)
747 def open_workingtree(self, _unsupported=False, recommend_upgrade=False):
749 return SvnWorkingTree(self, self.local_path, self.open_branch())
750 except NotSvnBranchPath, e:
751 raise NoWorkingTree(self.local_path)
753 def sprout(self, url, revision_id=None, force_new_repo=False,
754 recurse='down', possible_transports=None, accelerator_tree=None,
756 # FIXME: honor force_new_repo
758 result = get_rich_root_format().initialize(url)
759 repo = self._find_repository()
760 repo.clone(result, revision_id)
761 branch = self.open_branch()
762 branch.sprout(result, revision_id)
763 result.create_workingtree(hardlink=hardlink)
766 def open_repository(self):
767 raise NoRepositoryPresent(self)
769 def find_repository(self):
770 raise NoRepositoryPresent(self)
772 def _find_repository(self):
773 return SvnRepository(self, self.get_remote_transport().clone_root(),
774 self.get_remote_bzrdir().branch_path)
776 def needs_format_conversion(self, format=None):
778 format = BzrDirFormat.get_default_format()
779 return not isinstance(self._format, format.__class__)
781 def get_workingtree_transport(self, format):
782 assert format is None
783 return get_transport(self.svn_controldir)
785 def create_workingtree(self, revision_id=None, hardlink=None):
786 """See BzrDir.create_workingtree().
788 Not implemented for Subversion because having a .svn directory
789 implies having a working copy.
791 raise NotImplementedError(self.create_workingtree)
793 def create_branch(self):
794 """See BzrDir.create_branch()."""
795 raise NotImplementedError(self.create_branch)
797 def open_branch(self, unsupported=True):
798 """See BzrDir.open_branch()."""
799 repos = self._find_repository()
802 branch = SvnBranch(repos, self.get_remote_bzrdir().branch_path)
803 except SubversionException, (_, num):
804 if num == ERR_WC_NOT_DIRECTORY:
805 raise NotBranchError(path=self.base)
808 branch.bzrdir = self.get_remote_bzrdir()