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