681af296b6619a3ce3eb036d32c6991ede3cc6dd
[jelmer/subvertpy.git] / checkout.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 from binascii import hexlify
18 from bzrlib.branch import PullResult
19 from bzrlib.bzrdir import BzrDirFormat, BzrDir
20 from bzrlib.errors import (InvalidRevisionId, NotBranchError, NoSuchFile,
21                            NoRepositoryPresent, BzrError)
22 from bzrlib.inventory import (Inventory, InventoryDirectory, InventoryFile,
23                               InventoryLink, ROOT_ID)
24 from bzrlib.lockable_files import TransportLock, LockableFiles
25 from bzrlib.lockdir import LockDir
26 from bzrlib.osutils import rand_bytes, fingerprint_file
27 from bzrlib.progress import DummyProgress
28 from bzrlib.revision import NULL_REVISION
29 from bzrlib.trace import mutter
30 from bzrlib.transport.local import LocalTransport
31 from bzrlib.workingtree import WorkingTree, WorkingTreeFormat
32
33 from branch import SvnBranch
34 from repository import (SvnRepository, escape_svn_path, SVN_PROP_BZR_MERGE,
35                         SVN_PROP_SVK_MERGE, SVN_PROP_BZR_FILEIDS, 
36                         revision_id_to_svk_feature) 
37 from scheme import BranchingScheme
38 from transport import (SvnRaTransport, svn_config, bzr_to_svn_url, 
39                        _create_auth_baton) 
40 from tree import SvnBasisTree
41
42 from copy import copy
43 import os
44 import urllib
45
46 import svn.core, svn.wc
47 from svn.core import SubversionException, Pool
48
49 class WorkingTreeInconsistent(BzrError):
50     _fmt = """Working copy is in inconsistent state (%(min_revnum)d:%(max_revnum)d)"""
51
52     def __init__(self, min_revnum, max_revnum):
53         self.min_revnum = min_revnum
54         self.max_revnum = max_revnum
55
56
57 class SvnWorkingTree(WorkingTree):
58     """Implementation of WorkingTree that uses a Subversion 
59     Working Copy for storage."""
60     def __init__(self, bzrdir, local_path, branch):
61         self._format = SvnWorkingTreeFormat()
62         self.basedir = local_path
63         self.bzrdir = bzrdir
64         self._branch = branch
65         self.base_revnum = 0
66         self.pool = Pool()
67         self.client_ctx = svn.client.create_context()
68         self.client_ctx.config = svn_config
69         self.client_ctx.log_msg_func2 = svn.client.svn_swig_py_get_commit_log_func
70         self.client_ctx.auth_baton = _create_auth_baton(self.pool)
71
72         wc = self._get_wc()
73         status = svn.wc.revision_status(self.basedir, None, True, None, None)
74         if status.min_rev != status.max_rev:
75             #raise WorkingTreeInconsistent(status.min_rev, status.max_rev)
76             rev = svn.core.svn_opt_revision_t()
77             rev.kind = svn.core.svn_opt_revision_number
78             rev.value.number = status.max_rev
79             assert status.max_rev == svn.client.update(self.basedir, rev,
80                                      True, self.client_ctx, Pool())
81
82         self.base_revnum = status.max_rev
83         self.base_tree = SvnBasisTree(self)
84         self.base_revid = branch.repository.generate_revision_id(
85                     self.base_revnum, branch.branch_path)
86
87         self.read_working_inventory()
88
89         self.controldir = os.path.join(self.basedir, svn.wc.get_adm_dir(), 'bzr')
90         try:
91             os.makedirs(self.controldir)
92             os.makedirs(os.path.join(self.controldir, 'lock'))
93         except OSError:
94             pass
95         control_transport = bzrdir.transport.clone(os.path.join(svn.wc.get_adm_dir(), 'bzr'))
96         self._control_files = LockableFiles(control_transport, 'lock', LockDir)
97
98     def lock_write(self):
99         pass
100
101     def lock_read(self):
102         pass
103
104     def unlock(self):
105         pass
106
107     def get_ignore_list(self):
108         ignores = [svn.wc.get_adm_dir()] + svn.wc.get_default_ignores(svn_config)
109
110         def dir_add(wc, prefix):
111             ignorestr = svn.wc.prop_get(svn.core.SVN_PROP_IGNORE, self.abspath(prefix).rstrip("/"), wc)
112             if ignorestr is not None:
113                 for pat in ignorestr.splitlines():
114                     ignores.append("./"+os.path.join(prefix, pat))
115
116             entries = svn.wc.entries_read(wc, False)
117             for entry in entries:
118                 if entry == "":
119                     continue
120
121                 if entries[entry].kind != svn.core.svn_node_dir:
122                     continue
123
124                 subprefix = os.path.join(prefix, entry)
125
126                 subwc = svn.wc.adm_open3(wc, self.abspath(subprefix), False, 0, None)
127                 try:
128                     dir_add(subwc, subprefix)
129                 finally:
130                     svn.wc.adm_close(subwc)
131
132         wc = self._get_wc()
133         try:
134             dir_add(wc, "")
135         finally:
136             svn.wc.adm_close(wc)
137
138         return ignores
139
140     def _write_inventory(self, inv):
141         pass
142
143     def is_control_filename(self, path):
144         return svn.wc.is_adm_dir(path)
145
146     def remove(self, files, verbose=False, to_file=None):
147         assert isinstance(files, list)
148         wc = self._get_wc(write_lock=True)
149         try:
150             for file in files:
151                 svn.wc.delete2(self.abspath(file), wc, None, None, None)
152         finally:
153             svn.wc.adm_close(wc)
154
155         for file in files:
156             self._change_fileid_mapping(None, file)
157         self.read_working_inventory()
158
159     def _get_wc(self, relpath="", write_lock=False):
160         return svn.wc.adm_open3(None, self.abspath(relpath).rstrip("/"), 
161                                 write_lock, 0, None)
162
163     def _get_rel_wc(self, relpath, write_lock=False):
164         dir = os.path.dirname(relpath)
165         file = os.path.basename(relpath)
166         return (self._get_wc(dir, write_lock), file)
167
168     def move(self, from_paths, to_name):
169         revt = svn.core.svn_opt_revision_t()
170         revt.kind = svn.core.svn_opt_revision_working
171         for entry in from_paths:
172             try:
173                 to_wc = self._get_wc(to_name, write_lock=True)
174                 svn.wc.copy(self.abspath(entry), to_wc, 
175                             os.path.basename(entry), None, None)
176             finally:
177                 svn.wc.adm_close(to_wc)
178             try:
179                 from_wc = self._get_wc(write_lock=True)
180                 svn.wc.delete2(self.abspath(entry), from_wc, None, None, None)
181             finally:
182                 svn.wc.adm_close(from_wc)
183             new_name = "%s/%s" % (to_name, os.path.basename(entry))
184             self._change_fileid_mapping(self.inventory.path2id(entry), new_name)
185             self._change_fileid_mapping(None, entry)
186
187         self.read_working_inventory()
188
189     def rename_one(self, from_rel, to_rel):
190         revt = svn.core.svn_opt_revision_t()
191         revt.kind = svn.core.svn_opt_revision_unspecified
192         (to_wc, to_file) = self._get_rel_wc(to_rel, write_lock=True)
193         from_id = self.inventory.path2id(from_rel)
194         try:
195             svn.wc.copy(self.abspath(from_rel), to_wc, to_file, None, None)
196             svn.wc.delete2(self.abspath(from_rel), to_wc, None, None, None)
197         finally:
198             svn.wc.adm_close(to_wc)
199         self._change_fileid_mapping(None, from_rel)
200         self._change_fileid_mapping(from_id, to_rel)
201         self.read_working_inventory()
202
203     def path_to_file_id(self, revnum, current_revnum, path):
204         """Generate a bzr file id from a Subversion file name. 
205         
206         :param revnum: Revision number.
207         :param path: Absolute path.
208         :return: Tuple with file id and revision id.
209         """
210         assert isinstance(revnum, int) and revnum >= 0
211         assert isinstance(path, basestring)
212
213         (bp, rp) = self.branch.repository.scheme.unprefix(path)
214         entry = self.base_tree.id_map[rp]
215         assert entry[0] is not None
216         return entry
217
218     def read_working_inventory(self):
219         inv = Inventory()
220
221         def add_file_to_inv(relpath, id, revid, parent_id):
222             """Add a file to the inventory."""
223             if os.path.islink(self.abspath(relpath)):
224                 file = InventoryLink(id, os.path.basename(relpath), parent_id)
225                 file.revision = revid
226                 file.symlink_target = os.readlink(self.abspath(relpath))
227                 file.text_sha1 = None
228                 file.text_size = None
229                 file.executable = False
230                 inv.add(file)
231             else:
232                 file = InventoryFile(id, os.path.basename(relpath), parent_id)
233                 file.revision = revid
234                 try:
235                     data = fingerprint_file(open(self.abspath(relpath)))
236                     file.text_sha1 = data['sha1']
237                     file.text_size = data['size']
238                     file.executable = self.is_executable(id, relpath)
239                     inv.add(file)
240                 except IOError:
241                     # Ignore non-existing files
242                     pass
243
244         def find_copies(url, relpath=""):
245             wc = self._get_wc(relpath)
246             entries = svn.wc.entries_read(wc, False)
247             for entry in entries.values():
248                 subrelpath = os.path.join(relpath, entry.name)
249                 if entry.name == "" or entry.kind != 'directory':
250                     if ((entry.copyfrom_url == url or entry.url == url) and 
251                         not (entry.schedule in (svn.wc.schedule_delete,
252                                                 svn.wc.schedule_replace))):
253                         yield os.path.join(
254                                 self.branch.branch_path.strip("/"), 
255                                 subrelpath)
256                 else:
257                     find_copies(subrelpath)
258             svn.wc.adm_close(wc)
259
260         def find_ids(entry, rootwc):
261             relpath = urllib.unquote(entry.url[len(entry.repos):].strip("/"))
262             assert entry.schedule in (svn.wc.schedule_normal, 
263                                       svn.wc.schedule_delete,
264                                       svn.wc.schedule_add,
265                                       svn.wc.schedule_replace)
266             if entry.schedule == svn.wc.schedule_normal:
267                 assert entry.revision >= 0
268                 # Keep old id
269                 return self.path_to_file_id(entry.cmt_rev, entry.revision, 
270                         relpath)
271             elif entry.schedule == svn.wc.schedule_delete:
272                 return (None, None)
273             elif (entry.schedule == svn.wc.schedule_add or 
274                   entry.schedule == svn.wc.schedule_replace):
275                 # See if the file this file was copied from disappeared
276                 # and has no other copies -> in that case, take id of other file
277                 if entry.copyfrom_url and list(find_copies(entry.copyfrom_url)) == [relpath]:
278                     return self.path_to_file_id(entry.copyfrom_rev, entry.revision,
279                             entry.copyfrom_url[len(entry.repos):])
280                 ids = self._get_new_file_ids(rootwc)
281                 if ids.has_key(relpath):
282                     return (ids[relpath], None)
283                 return ("NEW-" + escape_svn_path(entry.url[len(entry.repos):].strip("/")), None)
284
285         def add_dir_to_inv(relpath, wc, parent_id):
286             entries = svn.wc.entries_read(wc, False)
287             entry = entries[""]
288             assert parent_id is None or isinstance(parent_id, str), "%r is not a string" % parent_id
289             (id, revid) = find_ids(entry, rootwc)
290             if id is None:
291                 mutter('no id for %r' % entry.url)
292                 return
293             assert isinstance(id, str), "%r is not a string" % id
294
295             # First handle directory itself
296             inv.add_path(relpath, 'directory', id, parent_id).revision = revid
297             if relpath == "":
298                 inv.revision_id = revid
299
300             for name in entries:
301                 if name == "":
302                     continue
303
304                 subrelpath = os.path.join(relpath, name)
305
306                 entry = entries[name]
307                 assert entry
308                 
309                 if entry.kind == svn.core.svn_node_dir:
310                     subwc = svn.wc.adm_open3(wc, self.abspath(subrelpath), 
311                                              False, 0, None)
312                     try:
313                         add_dir_to_inv(subrelpath, subwc, id)
314                     finally:
315                         svn.wc.adm_close(subwc)
316                 else:
317                     (subid, subrevid) = find_ids(entry, rootwc)
318                     if subid:
319                         add_file_to_inv(subrelpath, subid, subrevid, id)
320                     else:
321                         mutter('no id for %r' % entry.url)
322
323         rootwc = self._get_wc() 
324         try:
325             add_dir_to_inv("", rootwc, None)
326         finally:
327             svn.wc.adm_close(rootwc)
328
329         self._set_inventory(inv, dirty=False)
330         return inv
331
332     def set_last_revision(self, revid):
333         mutter('setting last revision to %r' % revid)
334         if revid is None or revid == NULL_REVISION:
335             self.base_revid = revid
336             self.base_revnum = 0
337             self.base_tree = RevisionTree(self, Inventory(), revid)
338             return
339
340         (bp, rev) = self.branch.repository.parse_revision_id(revid)
341         assert bp == self.branch.branch_path
342         self.base_revnum = rev
343         self.base_revid = revid
344         self.base_tree = SvnBasisTree(self)
345
346         # TODO: Implement more efficient version
347         newrev = self.branch.repository.get_revision(revid)
348         newrevtree = self.branch.repository.revision_tree(revid)
349
350         def update_settings(wc, path):
351             id = newrevtree.inventory.path2id(path)
352             mutter("Updating settings for %r" % id)
353             (_, revnum) = self.branch.repository.parse_revision_id(
354                     newrevtree.inventory[id].revision)
355
356             svn.wc.process_committed2(self.abspath(path).rstrip("/"), wc, 
357                           False, revnum, 
358                           svn.core.svn_time_to_cstring(newrev.timestamp), 
359                           newrev.committer, None, False)
360
361             if newrevtree.inventory[id].kind != 'directory':
362                 return
363
364             entries = svn.wc.entries_read(wc, True)
365             for entry in entries:
366                 if entry == "":
367                     continue
368
369                 subwc = svn.wc.adm_open3(wc, os.path.join(self.basedir, path, entry), False, 0, None)
370                 try:
371                     update_settings(subwc, os.path.join(path, entry))
372                 finally:
373                     svn.wc.adm_close(subwc)
374
375         # Set proper version for all files in the wc
376         wc = self._get_wc(write_lock=True)
377         try:
378             update_settings(wc, "")
379         finally:
380             svn.wc.adm_close(wc)
381         self.base_revid = revid
382
383     def commit(self, message=None, message_callback=None, revprops=None, timestamp=None, timezone=None, committer=None, 
384                rev_id=None, allow_pointless=True, strict=False, verbose=False, local=False, reporter=None, config=None, 
385                specific_files=None):
386         assert timestamp is None
387         assert timezone is None
388         assert rev_id is None
389
390         if specific_files:
391             specific_files = [self.abspath(x).encode('utf8') for x in specific_files]
392         else:
393             specific_files = [self.basedir.encode('utf8')]
394
395         if message_callback is not None:
396             def log_message_func(items, pool):
397                 """ Simple log message provider for unit tests. """
398                 return message_callback(self).encode("utf-8")
399         else:
400             assert isinstance(message, basestring)
401             def log_message_func(items, pool):
402                 """ Simple log message provider for unit tests. """
403                 return message.encode("utf-8")
404
405         self.client_ctx.log_msg_baton2 = log_message_func
406         commit_info = svn.client.commit3(specific_files, True, False, self.client_ctx)
407         self.client_ctx.log_msg_baton2 = None
408
409         revid = self.branch.repository.generate_revision_id(
410                 commit_info.revision, self.branch.branch_path)
411
412         self.base_revid = revid
413         self.base_revnum = commit_info.revision
414         self.base_tree = SvnBasisTree(self)
415
416         #FIXME: Use public API:
417         self.branch.revision_history()
418         self.branch._revision_history.append(revid)
419
420         return revid
421
422     def add(self, files, ids=None):
423         if isinstance(files, str):
424             files = [files]
425             if isinstance(ids, str):
426                 ids = [ids]
427         if ids:
428             ids = copy(ids)
429             ids.reverse()
430         assert isinstance(files, list)
431         for f in files:
432             try:
433                 wc = self._get_wc(os.path.dirname(f), write_lock=True)
434                 try:
435                     svn.wc.add2(os.path.join(self.basedir, f), wc, None, 0, 
436                             None, None, None)
437                     if ids:
438                         self._change_fileid_mapping(ids.pop(), f, wc)
439                 except SubversionException, (_, num):
440                     if num == svn.core.SVN_ERR_ENTRY_EXISTS:
441                         continue
442                     elif num == svn.core.SVN_ERR_WC_PATH_NOT_FOUND:
443                         raise NoSuchFile(path=f)
444                     raise
445             finally:
446                 svn.wc.adm_close(wc)
447         self.read_working_inventory()
448
449     def basis_tree(self):
450         if self.base_revid is None or self.base_revid == NULL_REVISION:
451             return self.branch.repository.revision_tree(self.base_revid)
452
453         return self.base_tree
454
455     def pull(self, source, overwrite=False, stop_revision=None, delta_reporter=None):
456         result = PullResult()
457         result.source_branch = source
458         result.master_branch = None
459         result.target_branch = self.branch
460         (result.old_revno, result.old_revid) = self.branch.last_revision_info()
461         if stop_revision is None:
462             stop_revision = self.branch.last_revision()
463         rev = svn.core.svn_opt_revision_t()
464         rev.kind = svn.core.svn_opt_revision_number
465         rev.value.number = self.branch.repository.parse_revision_id(stop_revision)[1]
466         fetched = svn.client.update(self.basedir, rev, True, self.client_ctx)
467         self.base_revid = self.branch.repository.generate_revision_id(fetched, self.branch.branch_path)
468         result.new_revid = self.branch.generate_revision_id(fetched)
469         result.new_revno = self.branch.revision_id_to_revno(result.new_revid)
470         return result
471
472     def get_file_sha1(self, file_id, path=None, stat_value=None):
473         if not path:
474             path = self._inventory.id2path(file_id)
475         return fingerprint_file(open(self.abspath(path)))['sha1']
476
477     def _change_fileid_mapping(self, id, path, wc=None):
478         if wc is None:
479             subwc = self._get_wc(write_lock=True)
480         else:
481             subwc = wc
482         new_entries = self._get_new_file_ids(subwc)
483         if id is None:
484             if new_entries.has_key(path):
485                 del new_entries[path]
486         else:
487             new_entries[path] = id
488         committed = self.branch.repository.branchprop_list.get_property(
489                 self.branch.branch_path, 
490                 self.base_revnum, 
491                 SVN_PROP_BZR_FILEIDS, "")
492         existing = committed + "".join(map(lambda (path, id): "%s\t%s\n" % (path, id), new_entries.items()))
493         if existing != "":
494             svn.wc.prop_set(SVN_PROP_BZR_FILEIDS, existing.encode("utf-8"), self.basedir, subwc)
495         if wc is None:
496             svn.wc.adm_close(subwc)
497
498     def _get_new_file_ids(self, wc):
499         committed = self.branch.repository.branchprop_list.get_property(
500                 self.branch.branch_path, 
501                 self.base_revnum, 
502                 SVN_PROP_BZR_FILEIDS, "")
503         existing = svn.wc.prop_get(SVN_PROP_BZR_FILEIDS, self.basedir, wc)
504         if existing is None:
505             return {}
506         else:
507             return dict(map(lambda x: x.split("\t"), existing[len(committed):].splitlines()))
508
509     def _get_bzr_merges(self):
510         return self.branch.repository.branchprop_list.get_property(
511                 self.branch.branch_path, 
512                 self.base_revnum, 
513                 SVN_PROP_BZR_MERGE, "")
514
515     def _get_svk_merges(self):
516         return self.branch.repository.branchprop_list.get_property(
517                 self.branch.branch_path, 
518                 self.base_revnum, 
519                 SVN_PROP_SVK_MERGE, "")
520
521     def set_pending_merges(self, merges):
522         wc = self._get_wc(write_lock=True)
523         try:
524             # Set bzr:merge
525             if len(merges) > 0:
526                 bzr_merge = "\t".join(merges) + "\n"
527             else:
528                 bzr_merge = ""
529
530             svn.wc.prop_set(SVN_PROP_BZR_MERGE, 
531                                  self._get_bzr_merges() + bzr_merge, 
532                                  self.basedir, wc)
533
534             # Set svk:merge
535             svk_merge = ""
536             for merge in merges:
537                 try:
538                     svk_merge += revision_id_to_svk_feature(merge) + "\n"
539                 except InvalidRevisionId:
540                     pass
541
542             svn.wc.prop_set2(SVN_PROP_SVK_MERGE, 
543                              self._get_svk_merges() + svk_merge, self.basedir, 
544                              wc, False)
545         finally:
546             svn.wc.adm_close(wc)
547
548     def add_pending_merge(self, revid):
549         merges = self.pending_merges()
550         merges.append(revid)
551         self.set_pending_merges(existing)
552
553     def pending_merges(self):
554         merged = self._get_bzr_merges().splitlines()
555         wc = self._get_wc()
556         try:
557             merged_data = svn.wc.prop_get(SVN_PROP_BZR_MERGE, self.basedir, wc)
558             if merged_data is None:
559                 set_merged = []
560             else:
561                 set_merged = merged_data.splitlines()
562         finally:
563             svn.wc.adm_close(wc)
564
565         assert (len(merged) == len(set_merged) or 
566                len(merged)+1 == len(set_merged))
567
568         if len(set_merged) > len(merged):
569             return set_merged[-1].split("\t")
570
571         return []
572
573
574 class SvnWorkingTreeFormat(WorkingTreeFormat):
575     def get_format_description(self):
576         return "Subversion Working Copy"
577
578     def initialize(self, a_bzrdir, revision_id=None):
579         raise NotImplementedError(self.initialize)
580
581     def open(self, a_bzrdir):
582         raise NotImplementedError(self.initialize)
583
584
585 class SvnCheckout(BzrDir):
586     """BzrDir implementation for Subversion checkouts (directories 
587     containing a .svn subdirectory."""
588     def __init__(self, transport, format):
589         super(SvnCheckout, self).__init__(transport, format)
590         self.local_path = transport.local_abspath(".")
591         
592         # Open related remote repository + branch
593         wc = svn.wc.adm_open3(None, self.local_path, False, 0, None)
594         try:
595             svn_url = svn.wc.entry(self.local_path, wc, True).url
596         finally:
597             svn.wc.adm_close(wc)
598
599         self.remote_transport = SvnRaTransport(svn_url)
600         self.svn_root_transport = SvnRaTransport(self.remote_transport.get_repos_root())
601         self.root_transport = self.transport = transport
602
603         self.branch_path = svn_url[len(bzr_to_svn_url(self.svn_root_transport.base)):]
604         self.scheme = BranchingScheme.guess_scheme(self.branch_path)
605         mutter('scheme for %r is %r' % (self.branch_path, self.scheme))
606         if not self.scheme.is_branch(self.branch_path) and not self.scheme.is_tag(self.branch_path):
607             raise NotBranchError(path=self.transport.base)
608
609     def clone(self, path):
610         raise NotImplementedError(self.clone)
611
612     def open_workingtree(self, _unsupported=False):
613         return SvnWorkingTree(self, self.local_path, self.open_branch())
614
615     def sprout(self, url, revision_id=None, basis=None, force_new_repo=False):
616         # FIXME: honor force_new_repo
617         result = BzrDirFormat.get_default_format().initialize(url)
618         repo = self.find_repository()
619         result_repo = repo.clone(result, revision_id, basis)
620         branch = self.open_branch()
621         branch.sprout(result, revision_id)
622         result.create_workingtree()
623         return result
624
625     def open_repository(self):
626         raise NoRepositoryPresent(self)
627
628     def find_repository(self):
629         return SvnRepository(self, self.svn_root_transport)
630
631     def create_workingtree(self, revision_id=None):
632         """See BzrDir.create_workingtree().
633
634         Not implemented for Subversion because having a .svn directory
635         implies having a working copy.
636         """
637         raise NotImplementedError(self.create_workingtree)
638
639     def create_branch(self):
640         """See BzrDir.create_branch()."""
641         raise NotImplementedError(self.create_branch)
642
643     def open_branch(self, unsupported=True):
644         """See BzrDir.open_branch()."""
645         repos = self.find_repository()
646
647         try:
648             branch = SvnBranch(self.root_transport.base, repos, self.branch_path)
649         except SubversionException, (msg, num):
650             if num == svn.core.SVN_ERR_WC_NOT_DIRECTORY:
651                raise NotBranchError(path=self.url)
652             raise
653  
654         branch.bzrdir = self
655         return branch
656
657
658 class SvnWorkingTreeDirFormat(BzrDirFormat):
659     """Working Tree implementation that uses Subversion working copies."""
660     _lock_class = TransportLock
661
662     @classmethod
663     def probe_transport(klass, transport):
664         format = klass()
665
666         if isinstance(transport, LocalTransport) and \
667             transport.has(svn.wc.get_adm_dir()):
668             return format
669
670         raise NotBranchError(path=transport.base)
671
672     def _open(self, transport):
673         return SvnCheckout(transport, self)
674
675     def get_format_string(self):
676         return 'Subversion Local Checkout'
677
678     def get_format_description(self):
679         return 'Subversion Local Checkout'
680
681     def initialize_on_transport(self, transport):
682         raise NotImplementedError(self.initialize_on_transport)