Update year
[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             if relpath == "":
297                 inv.add_path("", 'directory', ROOT_ID)
298                 inv.revision_id = revid
299             else:
300                 inventry = InventoryDirectory(id, os.path.basename(relpath), parent_id)
301                 inventry.revision = revid
302                 inv.add(inventry)
303
304             for name in entries:
305                 if name == "":
306                     continue
307
308                 subrelpath = os.path.join(relpath, name)
309
310                 entry = entries[name]
311                 assert entry
312                 
313                 if entry.kind == svn.core.svn_node_dir:
314                     subwc = svn.wc.adm_open3(wc, self.abspath(subrelpath), 
315                                              False, 0, None)
316                     try:
317                         add_dir_to_inv(subrelpath, subwc, id)
318                     finally:
319                         svn.wc.adm_close(subwc)
320                 else:
321                     (subid, subrevid) = find_ids(entry, rootwc)
322                     if subid:
323                         add_file_to_inv(subrelpath, subid, subrevid, id)
324                     else:
325                         mutter('no id for %r' % entry.url)
326
327         rootwc = self._get_wc() 
328         try:
329             add_dir_to_inv("", rootwc, None)
330         finally:
331             svn.wc.adm_close(rootwc)
332
333         self._set_inventory(inv, dirty=False)
334         return inv
335
336     def set_last_revision(self, revid):
337         mutter('setting last revision to %r' % revid)
338         if revid is None or revid == NULL_REVISION:
339             self.base_revid = revid
340             self.base_revnum = 0
341             self.base_tree = RevisionTree(self, Inventory(), revid)
342             return
343
344         (bp, rev) = self.branch.repository.parse_revision_id(revid)
345         assert bp == self.branch.branch_path
346         self.base_revnum = rev
347         self.base_revid = revid
348         self.base_tree = SvnBasisTree(self)
349
350         # TODO: Implement more efficient version
351         newrev = self.branch.repository.get_revision(revid)
352         newrevtree = self.branch.repository.revision_tree(revid)
353
354         def update_settings(wc, path):
355             id = newrevtree.inventory.path2id(path)
356             mutter("Updating settings for %r" % id)
357             (_, revnum) = self.branch.repository.parse_revision_id(
358                     newrevtree.inventory[id].revision)
359
360             svn.wc.process_committed2(self.abspath(path).rstrip("/"), wc, 
361                           False, revnum, 
362                           svn.core.svn_time_to_cstring(newrev.timestamp), 
363                           newrev.committer, None, False)
364
365             if newrevtree.inventory[id].kind != 'directory':
366                 return
367
368             entries = svn.wc.entries_read(wc, True)
369             for entry in entries:
370                 if entry == "":
371                     continue
372
373                 subwc = svn.wc.adm_open3(wc, os.path.join(self.basedir, path, entry), False, 0, None)
374                 try:
375                     update_settings(subwc, os.path.join(path, entry))
376                 finally:
377                     svn.wc.adm_close(subwc)
378
379         # Set proper version for all files in the wc
380         wc = self._get_wc(write_lock=True)
381         try:
382             update_settings(wc, "")
383         finally:
384             svn.wc.adm_close(wc)
385         self.base_revid = revid
386
387     def commit(self, message=None, message_callback=None, revprops=None, timestamp=None, timezone=None, committer=None, 
388                rev_id=None, allow_pointless=True, strict=False, verbose=False, local=False, reporter=None, config=None, 
389                specific_files=None):
390         assert timestamp is None
391         assert timezone is None
392         assert rev_id is None
393
394         if specific_files:
395             specific_files = [self.abspath(x).encode('utf8') for x in specific_files]
396         else:
397             specific_files = [self.basedir.encode('utf8')]
398
399         if message_callback is not None:
400             def log_message_func(items, pool):
401                 """ Simple log message provider for unit tests. """
402                 return message_callback(self).encode("utf-8")
403         else:
404             assert isinstance(message, basestring)
405             def log_message_func(items, pool):
406                 """ Simple log message provider for unit tests. """
407                 return message.encode("utf-8")
408
409         self.client_ctx.log_msg_baton2 = log_message_func
410         commit_info = svn.client.commit3(specific_files, True, False, self.client_ctx)
411         self.client_ctx.log_msg_baton2 = None
412
413         revid = self.branch.repository.generate_revision_id(
414                 commit_info.revision, self.branch.branch_path)
415
416         self.base_revid = revid
417         self.base_revnum = commit_info.revision
418         self.base_tree = SvnBasisTree(self)
419
420         #FIXME: Use public API:
421         self.branch.revision_history()
422         self.branch._revision_history.append(revid)
423
424         return revid
425
426     def add(self, files, ids=None):
427         if isinstance(files, str):
428             files = [files]
429             if isinstance(ids, str):
430                 ids = [ids]
431         if ids:
432             ids = copy(ids)
433             ids.reverse()
434         assert isinstance(files, list)
435         for f in files:
436             try:
437                 wc = self._get_wc(os.path.dirname(f), write_lock=True)
438                 try:
439                     svn.wc.add2(os.path.join(self.basedir, f), wc, None, 0, 
440                             None, None, None)
441                     if ids:
442                         self._change_fileid_mapping(ids.pop(), f, wc)
443                 except SubversionException, (_, num):
444                     if num == svn.core.SVN_ERR_ENTRY_EXISTS:
445                         continue
446                     elif num == svn.core.SVN_ERR_WC_PATH_NOT_FOUND:
447                         raise NoSuchFile(path=f)
448                     raise
449             finally:
450                 svn.wc.adm_close(wc)
451         self.read_working_inventory()
452
453     def basis_tree(self):
454         if self.base_revid is None or self.base_revid == NULL_REVISION:
455             return self.branch.repository.revision_tree(self.base_revid)
456
457         return self.base_tree
458
459     def pull(self, source, overwrite=False, stop_revision=None, delta_reporter=None):
460         result = PullResult()
461         result.source_branch = source
462         result.master_branch = None
463         result.target_branch = self.branch
464         (result.old_revno, result.old_revid) = self.branch.last_revision_info()
465         if stop_revision is None:
466             stop_revision = self.branch.last_revision()
467         rev = svn.core.svn_opt_revision_t()
468         rev.kind = svn.core.svn_opt_revision_number
469         rev.value.number = self.branch.repository.parse_revision_id(stop_revision)[1]
470         fetched = svn.client.update(self.basedir, rev, True, self.client_ctx)
471         self.base_revid = self.branch.repository.generate_revision_id(fetched, self.branch.branch_path)
472         result.new_revid = self.branch.generate_revision_id(fetched)
473         result.new_revno = self.branch.revision_id_to_revno(result.new_revid)
474         return result
475
476     def get_file_sha1(self, file_id, path=None, stat_value=None):
477         if not path:
478             path = self._inventory.id2path(file_id)
479         return fingerprint_file(open(self.abspath(path)))['sha1']
480
481     def _change_fileid_mapping(self, id, path, wc=None):
482         if wc is None:
483             subwc = self._get_wc(write_lock=True)
484         else:
485             subwc = wc
486         new_entries = self._get_new_file_ids(subwc)
487         if id is None:
488             if new_entries.has_key(path):
489                 del new_entries[path]
490         else:
491             new_entries[path] = id
492         committed = self.branch.repository.branchprop_list.get_property(
493                 self.branch.branch_path, 
494                 self.base_revnum, 
495                 SVN_PROP_BZR_FILEIDS, "")
496         existing = committed + "".join(map(lambda (path, id): "%s\t%s\n" % (path, id), new_entries.items()))
497         if existing != "":
498             svn.wc.prop_set(SVN_PROP_BZR_FILEIDS, existing.encode("utf-8"), self.basedir, subwc)
499         if wc is None:
500             svn.wc.adm_close(subwc)
501
502     def _get_new_file_ids(self, wc):
503         committed = self.branch.repository.branchprop_list.get_property(
504                 self.branch.branch_path, 
505                 self.base_revnum, 
506                 SVN_PROP_BZR_FILEIDS, "")
507         existing = svn.wc.prop_get(SVN_PROP_BZR_FILEIDS, self.basedir, wc)
508         if existing is None:
509             return {}
510         else:
511             return dict(map(lambda x: x.split("\t"), existing[len(committed):].splitlines()))
512
513     def _get_bzr_merges(self):
514         return self.branch.repository.branchprop_list.get_property(
515                 self.branch.branch_path, 
516                 self.base_revnum, 
517                 SVN_PROP_BZR_MERGE, "")
518
519     def _get_svk_merges(self):
520         return self.branch.repository.branchprop_list.get_property(
521                 self.branch.branch_path, 
522                 self.base_revnum, 
523                 SVN_PROP_SVK_MERGE, "")
524
525     def set_pending_merges(self, merges):
526         wc = self._get_wc(write_lock=True)
527         try:
528             # Set bzr:merge
529             if len(merges) > 0:
530                 bzr_merge = "\t".join(merges) + "\n"
531             else:
532                 bzr_merge = ""
533
534             svn.wc.prop_set(SVN_PROP_BZR_MERGE, 
535                                  self._get_bzr_merges() + bzr_merge, 
536                                  self.basedir, wc)
537
538             # Set svk:merge
539             svk_merge = ""
540             for merge in merges:
541                 try:
542                     svk_merge += revision_id_to_svk_feature(merge) + "\n"
543                 except InvalidRevisionId:
544                     pass
545
546             svn.wc.prop_set2(SVN_PROP_SVK_MERGE, 
547                              self._get_svk_merges() + svk_merge, self.basedir, 
548                              wc, False)
549         finally:
550             svn.wc.adm_close(wc)
551
552     def add_pending_merge(self, revid):
553         merges = self.pending_merges()
554         merges.append(revid)
555         self.set_pending_merges(existing)
556
557     def pending_merges(self):
558         merged = self._get_bzr_merges().splitlines()
559         wc = self._get_wc()
560         try:
561             merged_data = svn.wc.prop_get(SVN_PROP_BZR_MERGE, self.basedir, wc)
562             if merged_data is None:
563                 set_merged = []
564             else:
565                 set_merged = merged_data.splitlines()
566         finally:
567             svn.wc.adm_close(wc)
568
569         assert (len(merged) == len(set_merged) or 
570                len(merged)+1 == len(set_merged))
571
572         if len(set_merged) > len(merged):
573             return set_merged[-1].split("\t")
574
575         return []
576
577
578 class SvnWorkingTreeFormat(WorkingTreeFormat):
579     def get_format_description(self):
580         return "Subversion Working Copy"
581
582     def initialize(self, a_bzrdir, revision_id=None):
583         raise NotImplementedError(self.initialize)
584
585     def open(self, a_bzrdir):
586         raise NotImplementedError(self.initialize)
587
588
589 class SvnCheckout(BzrDir):
590     """BzrDir implementation for Subversion checkouts (directories 
591     containing a .svn subdirectory."""
592     def __init__(self, transport, format):
593         super(SvnCheckout, self).__init__(transport, format)
594         self.local_path = transport.local_abspath(".")
595         
596         # Open related remote repository + branch
597         wc = svn.wc.adm_open3(None, self.local_path, False, 0, None)
598         try:
599             svn_url = svn.wc.entry(self.local_path, wc, True).url
600         finally:
601             svn.wc.adm_close(wc)
602
603         self.remote_transport = SvnRaTransport(svn_url)
604         self.svn_root_transport = SvnRaTransport(self.remote_transport.get_repos_root())
605         self.root_transport = self.transport = transport
606
607         self.branch_path = svn_url[len(bzr_to_svn_url(self.svn_root_transport.base)):]
608         self.scheme = BranchingScheme.guess_scheme(self.branch_path)
609         mutter('scheme for %r is %r' % (self.branch_path, self.scheme))
610         if not self.scheme.is_branch(self.branch_path):
611             raise NotBranchError(path=self.transport.base)
612
613     def clone(self, path):
614         raise NotImplementedError(self.clone)
615
616     def open_workingtree(self, _unsupported=False):
617         return SvnWorkingTree(self, self.local_path, self.open_branch())
618
619     def sprout(self, url, revision_id=None, basis=None, force_new_repo=False):
620         # FIXME: honor force_new_repo
621         result = BzrDirFormat.get_default_format().initialize(url)
622         repo = self.find_repository()
623         result_repo = repo.clone(result, revision_id, basis)
624         branch = self.open_branch()
625         branch.sprout(result, revision_id)
626         result.create_workingtree()
627         return result
628
629     def open_repository(self):
630         raise NoRepositoryPresent(self)
631
632     def find_repository(self):
633         return SvnRepository(self, self.svn_root_transport)
634
635     def create_workingtree(self, revision_id=None):
636         """See BzrDir.create_workingtree().
637
638         Not implemented for Subversion because having a .svn directory
639         implies having a working copy.
640         """
641         raise NotImplementedError(self.create_workingtree)
642
643     def create_branch(self):
644         """See BzrDir.create_branch()."""
645         raise NotImplementedError(self.create_branch)
646
647     def open_branch(self, unsupported=True):
648         """See BzrDir.open_branch()."""
649         repos = self.find_repository()
650
651         try:
652             branch = SvnBranch(self.root_transport.base, repos, self.branch_path)
653         except SubversionException, (msg, num):
654             if num == svn.core.SVN_ERR_WC_NOT_DIRECTORY:
655                raise NotBranchError(path=self.url)
656             raise
657  
658         branch.bzrdir = self
659         return branch
660
661
662 class SvnWorkingTreeDirFormat(BzrDirFormat):
663     """Working Tree implementation that uses Subversion working copies."""
664     _lock_class = TransportLock
665
666     @classmethod
667     def probe_transport(klass, transport):
668         format = klass()
669
670         if isinstance(transport, LocalTransport) and \
671             transport.has(svn.wc.get_adm_dir()):
672             return format
673
674         raise NotBranchError(path=transport.base)
675
676     def _open(self, transport):
677         return SvnCheckout(transport, self)
678
679     def get_format_string(self):
680         return 'Subversion Local Checkout'
681
682     def get_format_description(self):
683         return 'Subversion Local Checkout'
684
685     def initialize_on_transport(self, transport):
686         raise NotImplementedError(self.initialize_on_transport)