1 # Copyright (C) 2005-2007 Jelmer Vernooij <jelmer@samba.org>
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software
15 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16 """Checkouts and working trees (working copies)."""
19 from bzrlib.branch import PullResult
20 from bzrlib.bzrdir import BzrDirFormat, BzrDir
21 from bzrlib.errors import (InvalidRevisionId, NotBranchError, NoSuchFile,
22 NoRepositoryPresent, BzrError)
23 from bzrlib.inventory import Inventory, InventoryFile, InventoryLink
24 from bzrlib.lockable_files import TransportLock, LockableFiles
25 from bzrlib.lockdir import LockDir
26 from bzrlib.osutils import file_kind, fingerprint_file
27 from bzrlib.revision import NULL_REVISION
28 from bzrlib.trace import mutter
29 from bzrlib.tree import RevisionTree
30 from bzrlib.transport.local import LocalTransport
31 from bzrlib.workingtree import WorkingTree, WorkingTreeFormat
33 from branch import SvnBranch
34 from convert import SvnConverter
35 from errors import LocalCommitsUnsupported
36 from repository import (SvnRepository, SVN_PROP_BZR_MERGE,
37 SVN_PROP_SVK_MERGE, SVN_PROP_BZR_FILEIDS,
38 SVN_PROP_BZR_REVISION_ID, SVN_PROP_BZR_REVISION_INFO,
39 revision_id_to_svk_feature, generate_revision_metadata)
40 from revids import escape_svn_path
41 from scheme import BranchingScheme
42 from transport import (SvnRaTransport, svn_config, bzr_to_svn_url,
44 from tree import SvnBasisTree
50 import svn.core, svn.wc
51 from svn.core import SubversionException, Pool
53 from errors import NoCheckoutSupport
54 from format import get_rich_root_format
56 class WorkingTreeInconsistent(BzrError):
57 _fmt = """Working copy is in inconsistent state (%(min_revnum)d:%(max_revnum)d)"""
59 def __init__(self, min_revnum, max_revnum):
60 self.min_revnum = min_revnum
61 self.max_revnum = max_revnum
64 class SvnWorkingTree(WorkingTree):
65 """WorkingTree implementation that uses a Subversion Working Copy for storage."""
66 def __init__(self, bzrdir, local_path, branch):
67 self._format = SvnWorkingTreeFormat()
68 self.basedir = local_path
73 self.client_ctx = svn.client.create_context()
74 self.client_ctx.config = svn_config
75 self.client_ctx.log_msg_func2 = \
76 svn.client.svn_swig_py_get_commit_log_func
77 self.client_ctx.auth_baton = _create_auth_baton(self.pool)
80 status = svn.wc.revision_status(self.basedir, None, True, None, None)
81 if status.min_rev != status.max_rev:
82 #raise WorkingTreeInconsistent(status.min_rev, status.max_rev)
83 rev = svn.core.svn_opt_revision_t()
84 rev.kind = svn.core.svn_opt_revision_number
85 rev.value.number = status.max_rev
86 assert status.max_rev == svn.client.update(self.basedir, rev,
87 True, self.client_ctx, Pool())
89 self.base_revnum = status.max_rev
90 self.base_tree = SvnBasisTree(self)
91 self.base_revid = branch.generate_revision_id(self.base_revnum)
93 self.read_working_inventory()
95 self.controldir = os.path.join(self.basedir, svn.wc.get_adm_dir(),
98 os.makedirs(self.controldir)
99 os.makedirs(os.path.join(self.controldir, 'lock'))
102 control_transport = bzrdir.transport.clone(os.path.join(
103 svn.wc.get_adm_dir(), 'bzr'))
104 self._control_files = LockableFiles(control_transport, 'lock', LockDir)
106 def get_ignore_list(self):
107 ignores = set([svn.wc.get_adm_dir()])
108 ignores.update(svn.wc.get_default_ignores(svn_config))
110 def dir_add(wc, prefix):
111 ignorestr = svn.wc.prop_get(svn.core.SVN_PROP_IGNORE,
112 self.abspath(prefix).rstrip("/"), wc)
113 if ignorestr is not None:
114 for pat in ignorestr.splitlines():
115 ignores.add("./"+os.path.join(prefix, pat))
117 entries = svn.wc.entries_read(wc, False)
118 for entry in entries:
122 if entries[entry].kind != svn.core.svn_node_dir:
125 subprefix = os.path.join(prefix, entry)
127 subwc = svn.wc.adm_open3(wc, self.abspath(subprefix), False,
130 dir_add(subwc, subprefix)
132 svn.wc.adm_close(subwc)
142 def is_control_filename(self, path):
143 return svn.wc.is_adm_dir(path)
145 def apply_inventory_delta(self, changes):
146 raise NotImplementedError(self.apply_inventory_delta)
148 def remove(self, files, verbose=False, to_file=None):
149 # FIXME: Use to_file argument
150 # FIXME: Use verbose argument
151 assert isinstance(files, list)
152 wc = self._get_wc(write_lock=True)
155 svn.wc.delete2(self.abspath(file), wc, None, None, None)
160 self._change_fileid_mapping(None, file)
161 self.read_working_inventory()
163 def _get_wc(self, relpath="", write_lock=False):
164 return svn.wc.adm_open3(None, self.abspath(relpath).rstrip("/"),
167 def _get_rel_wc(self, relpath, write_lock=False):
168 dir = os.path.dirname(relpath)
169 file = os.path.basename(relpath)
170 return (self._get_wc(dir, write_lock), file)
172 def move(self, from_paths, to_dir=None, after=False, **kwargs):
173 # FIXME: Use after argument
175 revt = svn.core.svn_opt_revision_t()
176 revt.kind = svn.core.svn_opt_revision_working
177 for entry in from_paths:
179 to_wc = self._get_wc(to_dir, write_lock=True)
180 svn.wc.copy(self.abspath(entry), to_wc,
181 os.path.basename(entry), None, None)
183 svn.wc.adm_close(to_wc)
185 from_wc = self._get_wc(write_lock=True)
186 svn.wc.delete2(self.abspath(entry), from_wc, None, None, None)
188 svn.wc.adm_close(from_wc)
189 new_name = "%s/%s" % (to_dir, os.path.basename(entry))
190 self._change_fileid_mapping(self.inventory.path2id(entry), new_name)
191 self._change_fileid_mapping(None, entry)
193 self.read_working_inventory()
195 def rename_one(self, from_rel, to_rel, after=False):
198 revt = svn.core.svn_opt_revision_t()
199 revt.kind = svn.core.svn_opt_revision_unspecified
200 (to_wc, to_file) = self._get_rel_wc(to_rel, write_lock=True)
201 from_id = self.inventory.path2id(from_rel)
203 svn.wc.copy(self.abspath(from_rel), to_wc, to_file, None, None)
204 svn.wc.delete2(self.abspath(from_rel), to_wc, None, None, None)
206 svn.wc.adm_close(to_wc)
207 self._change_fileid_mapping(None, from_rel)
208 self._change_fileid_mapping(from_id, to_rel)
209 self.read_working_inventory()
211 def path_to_file_id(self, revnum, current_revnum, path):
212 """Generate a bzr file id from a Subversion file name.
214 :param revnum: Revision number.
215 :param path: Absolute path.
216 :return: Tuple with file id and revision id.
218 assert isinstance(revnum, int) and revnum >= 0
219 assert isinstance(path, basestring)
221 (_, rp) = self.branch.scheme.unprefix(path)
222 entry = self.base_tree.id_map[rp]
223 assert entry[0] is not None
224 assert isinstance(entry[0], str), "fileid %r for %r is not a string" % (entry[0], path)
227 def read_working_inventory(self):
230 def add_file_to_inv(relpath, id, revid, parent_id):
231 """Add a file to the inventory."""
232 if os.path.islink(self.abspath(relpath)):
233 file = InventoryLink(id, os.path.basename(relpath), parent_id)
234 file.revision = revid
235 file.symlink_target = os.readlink(self.abspath(relpath))
236 file.text_sha1 = None
237 file.text_size = None
238 file.executable = False
241 file = InventoryFile(id, os.path.basename(relpath), parent_id)
242 file.revision = revid
244 data = fingerprint_file(open(self.abspath(relpath)))
245 file.text_sha1 = data['sha1']
246 file.text_size = data['size']
247 file.executable = self.is_executable(id, relpath)
250 # Ignore non-existing files
253 def find_copies(url, relpath=""):
254 wc = self._get_wc(relpath)
255 entries = svn.wc.entries_read(wc, False)
256 for entry in entries.values():
257 subrelpath = os.path.join(relpath, entry.name)
258 if entry.name == "" or entry.kind != 'directory':
259 if ((entry.copyfrom_url == url or entry.url == url) and
260 not (entry.schedule in (svn.wc.schedule_delete,
261 svn.wc.schedule_replace))):
263 self.branch.branch_path.strip("/"),
266 find_copies(subrelpath)
269 def find_ids(entry, rootwc):
270 relpath = urllib.unquote(entry.url[len(entry.repos):].strip("/"))
271 assert entry.schedule in (svn.wc.schedule_normal,
272 svn.wc.schedule_delete,
274 svn.wc.schedule_replace)
275 if entry.schedule == svn.wc.schedule_normal:
276 assert entry.revision >= 0
278 return self.path_to_file_id(entry.cmt_rev, entry.revision,
280 elif entry.schedule == svn.wc.schedule_delete:
282 elif (entry.schedule == svn.wc.schedule_add or
283 entry.schedule == svn.wc.schedule_replace):
284 # See if the file this file was copied from disappeared
285 # and has no other copies -> in that case, take id of other file
286 if (entry.copyfrom_url and
287 list(find_copies(entry.copyfrom_url)) == [relpath]):
288 return self.path_to_file_id(entry.copyfrom_rev,
289 entry.revision, entry.copyfrom_url[len(entry.repos):])
290 ids = self._get_new_file_ids(rootwc)
291 if ids.has_key(relpath):
292 return (ids[relpath], None)
293 return ("NEW-" + escape_svn_path(entry.url[len(entry.repos):].strip("/")), None)
295 def add_dir_to_inv(relpath, wc, parent_id):
296 entries = svn.wc.entries_read(wc, False)
298 assert parent_id is None or isinstance(parent_id, str), \
299 "%r is not a string" % parent_id
300 (id, revid) = find_ids(entry, rootwc)
302 mutter('no id for %r' % entry.url)
304 assert revid is None or isinstance(revid, str), "%r is not a string" % revid
305 assert isinstance(id, str), "%r is not a string" % id
307 # First handle directory itself
308 inv.add_path(relpath, 'directory', id, parent_id).revision = revid
310 inv.revision_id = revid
316 subrelpath = os.path.join(relpath, name)
318 entry = entries[name]
321 if entry.kind == svn.core.svn_node_dir:
322 subwc = svn.wc.adm_open3(wc, self.abspath(subrelpath),
325 add_dir_to_inv(subrelpath, subwc, id)
327 svn.wc.adm_close(subwc)
329 (subid, subrevid) = find_ids(entry, rootwc)
331 add_file_to_inv(subrelpath, subid, subrevid, id)
333 mutter('no id for %r' % entry.url)
335 rootwc = self._get_wc()
337 add_dir_to_inv("", rootwc, None)
339 svn.wc.adm_close(rootwc)
341 self._set_inventory(inv, dirty=False)
344 def set_last_revision(self, revid):
345 mutter('setting last revision to %r' % revid)
346 if revid is None or revid == NULL_REVISION:
347 self.base_revid = revid
349 self.base_tree = RevisionTree(self, Inventory(), revid)
352 rev = self.branch.lookup_revision_id(revid)
353 self.base_revnum = rev
354 self.base_revid = revid
355 self.base_tree = SvnBasisTree(self)
357 # TODO: Implement more efficient version
358 newrev = self.branch.repository.get_revision(revid)
359 newrevtree = self.branch.repository.revision_tree(revid)
361 def update_settings(wc, path):
362 id = newrevtree.inventory.path2id(path)
363 mutter("Updating settings for %r" % id)
364 revnum = self.branch.lookup_revision_id(
365 newrevtree.inventory[id].revision)
367 svn.wc.process_committed2(self.abspath(path).rstrip("/"), wc,
369 svn.core.svn_time_to_cstring(newrev.timestamp),
370 newrev.committer, None, False)
372 if newrevtree.inventory[id].kind != 'directory':
375 entries = svn.wc.entries_read(wc, True)
376 for entry in entries:
380 subwc = svn.wc.adm_open3(wc, os.path.join(self.basedir, path, entry), False, 0, None)
382 update_settings(subwc, os.path.join(path, entry))
384 svn.wc.adm_close(subwc)
386 # Set proper version for all files in the wc
387 wc = self._get_wc(write_lock=True)
389 update_settings(wc, "")
392 self.base_revid = revid
394 def commit(self, message=None, message_callback=None, revprops=None,
395 timestamp=None, timezone=None, committer=None, rev_id=None,
396 allow_pointless=True, strict=False, verbose=False, local=False,
397 reporter=None, config=None, specific_files=None):
398 # FIXME: Use allow_pointless
400 # FIXME: Use reporter
403 raise LocalCommitsUnsupported()
406 specific_files = [self.abspath(x).encode('utf8') for x in specific_files]
408 specific_files = [self.basedir.encode('utf8')]
410 if message_callback is not None:
411 def log_message_func(items, pool):
412 """ Simple log message provider for unit tests. """
413 return message_callback(self).encode("utf-8")
415 assert isinstance(message, basestring)
416 def log_message_func(items, pool):
417 """ Simple log message provider for unit tests. """
418 return message.encode("utf-8")
420 self.client_ctx.log_msg_baton2 = log_message_func
421 if rev_id is not None:
422 extra = "%d %s\n" % (self.branch.revno()+1, rev_id)
425 wc = self._get_wc(write_lock=True)
427 svn.wc.prop_set(SVN_PROP_BZR_REVISION_ID+str(self.branch.scheme),
428 self._get_bzr_revids() + extra,
430 svn.wc.prop_set(SVN_PROP_BZR_REVISION_INFO,
431 generate_revision_metadata(timestamp,
440 commit_info = svn.client.commit3(specific_files, True, False,
443 # Reset properties so the next subversion commit won't
444 # accidently set these properties.
445 wc = self._get_wc(write_lock=True)
446 svn.wc.prop_set(SVN_PROP_BZR_REVISION_ID+str(self.branch.scheme),
447 self._get_bzr_revids(), self.basedir, wc)
448 svn.wc.prop_set(SVN_PROP_BZR_REVISION_INFO,
449 self.branch.repository.branchprop_list.get_property(
450 self.branch.branch_path, self.base_revnum,
451 SVN_PROP_BZR_REVISION_INFO, ""),
456 self.client_ctx.log_msg_baton2 = None
458 revid = self.branch.generate_revision_id(commit_info.revision)
460 self.base_revid = revid
461 self.base_revnum = commit_info.revision
462 self.base_tree = SvnBasisTree(self)
464 self.branch.repository._latest_revnum = commit_info.revision
466 #FIXME: Use public API:
467 if self.branch._revision_history is not None:
468 self.branch._revision_history.append(revid)
472 def smart_add(self, file_list, recurse=True, action=None, save=True):
473 assert isinstance(recurse, bool)
475 action = bzrlib.add.AddAction()
478 # no paths supplied: add the entire tree.
483 for file_path in file_list:
485 file_path = os.path.abspath(file_path)
486 f = self.relpath(file_path)
487 wc = self._get_wc(os.path.dirname(f), write_lock=True)
489 if not self.inventory.has_filename(f):
491 mutter('adding %r' % file_path)
492 svn.wc.add2(file_path, wc, None, 0, None, None, None)
493 added.append(file_path)
494 if recurse and file_kind(file_path) == 'directory':
495 # Filter out ignored files and update ignored
496 for c in os.listdir(file_path):
497 if self.is_control_filename(c):
499 c_path = os.path.join(file_path, c)
500 ignore_glob = self.is_ignored(c)
501 if ignore_glob is not None:
502 ignored.setdefault(ignore_glob, []).append(c_path)
507 cadded, cignored = self.smart_add(todo, recurse, action, save)
509 ignored.update(cignored)
510 return added, ignored
512 def add(self, files, ids=None, kinds=None):
514 if isinstance(files, str):
516 if isinstance(ids, str):
521 assert isinstance(files, list)
523 wc = self._get_wc(os.path.dirname(f), write_lock=True)
526 svn.wc.add2(os.path.join(self.basedir, f), wc, None, 0,
529 self._change_fileid_mapping(ids.pop(), f, wc)
530 except SubversionException, (_, num):
531 if num == svn.core.SVN_ERR_ENTRY_EXISTS:
533 elif num == svn.core.SVN_ERR_WC_PATH_NOT_FOUND:
534 raise NoSuchFile(path=f)
538 self.read_working_inventory()
540 def basis_tree(self):
541 if self.base_revid is None or self.base_revid == NULL_REVISION:
542 return self.branch.repository.revision_tree(self.base_revid)
544 return self.base_tree
546 def pull(self, source, overwrite=False, stop_revision=None,
547 delta_reporter=None):
548 # FIXME: Use delta_reporter
549 # FIXME: Use overwrite
550 result = PullResult()
551 result.source_branch = source
552 result.master_branch = None
553 result.target_branch = self.branch
554 (result.old_revno, result.old_revid) = self.branch.last_revision_info()
555 if stop_revision is None:
556 stop_revision = self.branch.last_revision()
557 rev = svn.core.svn_opt_revision_t()
558 rev.kind = svn.core.svn_opt_revision_number
559 rev.value.number = self.branch.lookup_revision_id(stop_revision)
560 fetched = svn.client.update(self.basedir, rev, True, self.client_ctx)
561 self.base_revid = self.branch.repository.generate_revision_id(fetched, self.branch.branch_path)
562 result.new_revid = self.branch.generate_revision_id(fetched)
563 result.new_revno = self.branch.revision_id_to_revno(result.new_revid)
566 def get_file_sha1(self, file_id, path=None, stat_value=None):
568 path = self._inventory.id2path(file_id)
569 return fingerprint_file(open(self.abspath(path)))['sha1']
571 def _change_fileid_mapping(self, id, path, wc=None):
573 subwc = self._get_wc(write_lock=True)
576 new_entries = self._get_new_file_ids(subwc)
578 if new_entries.has_key(path):
579 del new_entries[path]
581 assert isinstance(id, str)
582 new_entries[path] = id
583 committed = self.branch.repository.branchprop_list.get_property(
584 self.branch.branch_path,
586 SVN_PROP_BZR_FILEIDS, "")
587 existing = committed + "".join(map(lambda (path, id): "%s\t%s\n" % (path, id), new_entries.items()))
589 svn.wc.prop_set(SVN_PROP_BZR_FILEIDS, existing.encode("utf-8"), self.basedir, subwc)
591 svn.wc.adm_close(subwc)
593 def _get_new_file_ids(self, wc):
594 committed = self.branch.repository.branchprop_list.get_property(
595 self.branch.branch_path, self.base_revnum,
596 SVN_PROP_BZR_FILEIDS, "")
597 existing = svn.wc.prop_get(SVN_PROP_BZR_FILEIDS, self.basedir, wc)
601 return dict(map(lambda x: str(x).split("\t"),
602 existing[len(committed):].splitlines()))
604 def _get_bzr_revids(self):
605 return self.branch.repository.branchprop_list.get_property(
606 self.branch.branch_path, self.base_revnum,
607 SVN_PROP_BZR_REVISION_ID+str(self.branch.scheme), "")
609 def _get_bzr_merges(self):
610 return self.branch.repository.branchprop_list.get_property(
611 self.branch.branch_path, self.base_revnum,
612 SVN_PROP_BZR_MERGE, "")
614 def _get_svk_merges(self):
615 return self.branch.repository.branchprop_list.get_property(
616 self.branch.branch_path, self.base_revnum,
617 SVN_PROP_SVK_MERGE, "")
619 def set_pending_merges(self, merges):
620 wc = self._get_wc(write_lock=True)
624 bzr_merge = "\t".join(merges) + "\n"
628 svn.wc.prop_set(SVN_PROP_BZR_MERGE,
629 self._get_bzr_merges() + bzr_merge,
636 svk_merge += revision_id_to_svk_feature(merge) + "\n"
637 except InvalidRevisionId:
640 svn.wc.prop_set2(SVN_PROP_SVK_MERGE,
641 self._get_svk_merges() + svk_merge, self.basedir,
646 def add_pending_merge(self, revid):
647 merges = self.pending_merges()
649 self.set_pending_merges(merges)
651 def pending_merges(self):
652 merged = self._get_bzr_merges().splitlines()
655 merged_data = svn.wc.prop_get(SVN_PROP_BZR_MERGE, self.basedir, wc)
656 if merged_data is None:
659 set_merged = merged_data.splitlines()
663 assert (len(merged) == len(set_merged) or
664 len(merged)+1 == len(set_merged))
666 if len(set_merged) > len(merged):
667 return set_merged[-1].split("\t")
671 def _reset_data(self):
675 # reverse order of locking.
677 return self._control_files.unlock()
683 class SvnWorkingTreeFormat(WorkingTreeFormat):
684 """Subversion working copy format."""
685 def get_format_description(self):
686 return "Subversion Working Copy"
688 def initialize(self, a_bzrdir, revision_id=None):
689 raise NotImplementedError(self.initialize)
691 def open(self, a_bzrdir):
692 raise NotImplementedError(self.initialize)
695 class SvnCheckout(BzrDir):
696 """BzrDir implementation for Subversion checkouts (directories
697 containing a .svn subdirectory."""
698 def __init__(self, transport, format):
699 super(SvnCheckout, self).__init__(transport, format)
700 self.local_path = transport.local_abspath(".")
702 # Open related remote repository + branch
703 wc = svn.wc.adm_open3(None, self.local_path, False, 0, None)
705 svn_url = svn.wc.entry(self.local_path, wc, True).url
709 self.remote_transport = SvnRaTransport(svn_url)
710 self.svn_root_transport = SvnRaTransport(self.remote_transport.get_repos_root())
711 self.root_transport = self.transport = transport
713 self.branch_path = svn_url[len(bzr_to_svn_url(self.svn_root_transport.base)):]
715 def clone(self, path, revision_id=None, force_new_repo=False):
716 raise NotImplementedError(self.clone)
718 def open_workingtree(self, _unsupported=False, recommend_upgrade=False):
719 return SvnWorkingTree(self, self.local_path, self.open_branch())
721 def sprout(self, url, revision_id=None, force_new_repo=False,
723 # FIXME: honor force_new_repo
725 result = get_rich_root_format().initialize(url)
726 repo = self.find_repository()
727 repo.clone(result, revision_id)
728 branch = self.open_branch()
729 branch.sprout(result, revision_id)
730 result.create_workingtree()
733 def open_repository(self):
734 raise NoRepositoryPresent(self)
736 def find_repository(self):
737 guess_scheme = BranchingScheme.guess_scheme(self.branch_path)
738 return SvnRepository(self, self.svn_root_transport,
741 def create_workingtree(self, revision_id=None):
742 """See BzrDir.create_workingtree().
744 Not implemented for Subversion because having a .svn directory
745 implies having a working copy.
747 raise NotImplementedError(self.create_workingtree)
749 def create_branch(self):
750 """See BzrDir.create_branch()."""
751 raise NotImplementedError(self.create_branch)
753 def open_branch(self, unsupported=True):
754 """See BzrDir.open_branch()."""
755 repos = self.find_repository()
758 branch = SvnBranch(self.root_transport.base, repos, self.branch_path,
760 except SubversionException, (_, num):
761 if num == svn.core.SVN_ERR_WC_NOT_DIRECTORY:
762 raise NotBranchError(path=self.base)
769 class SvnWorkingTreeDirFormat(BzrDirFormat):
770 """Working Tree implementation that uses Subversion working copies."""
771 _lock_class = TransportLock
774 def probe_transport(klass, transport):
777 if isinstance(transport, LocalTransport) and \
778 transport.has(svn.wc.get_adm_dir()):
779 subr_version = svn.core.svn_subr_version()
780 if subr_version.major == 1 and subr_version.minor < 4:
781 raise NoCheckoutSupport()
784 raise NotBranchError(path=transport.base)
786 def _open(self, transport):
787 return SvnCheckout(transport, self)
789 def get_format_string(self):
790 return 'Subversion Local Checkout'
792 def get_format_description(self):
793 return 'Subversion Local Checkout'
795 def initialize_on_transport(self, transport):
796 raise NotImplementedError(self.initialize_on_transport)
798 def get_converter(self, format=None):
799 """See BzrDirFormat.get_converter()."""
801 format = get_rich_root_format()
802 return SvnConverter(format)