1 # Copyright (C) 2006 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 """Subversion repository access."""
19 from bzrlib import osutils
20 from bzrlib.branch import BranchCheckResult
21 from bzrlib.errors import (InvalidRevisionId, NoSuchRevision,
22 NotBranchError, UninitializableFormat, BzrError)
23 from bzrlib.inventory import Inventory
24 from bzrlib.lockable_files import LockableFiles, TransportLock
25 from bzrlib.repository import Repository, RepositoryFormat
26 from bzrlib.revisiontree import RevisionTree
27 from bzrlib.revision import Revision, NULL_REVISION
28 from bzrlib.transport import Transport
29 from bzrlib.timestamp import unpack_highres_date, format_highres_date
30 from bzrlib.trace import mutter
32 from svn.core import SubversionException, Pool
37 from branchprops import BranchPropertyList
38 from cache import create_cache_dir, sqlite3
39 from config import SvnRepositoryConfig
42 from revids import (generate_svn_revision_id, parse_svn_revision_id,
43 MAPPING_VERSION, RevidMap)
44 from scheme import BranchingScheme, ListBranchingScheme, parse_list_scheme_text
45 from tree import SvnRevisionTree
47 SVN_PROP_BZR_PREFIX = 'bzr:'
48 SVN_PROP_BZR_MERGE = 'bzr:merge'
49 SVN_PROP_BZR_FILEIDS = 'bzr:file-ids'
50 SVN_PROP_SVK_MERGE = 'svk:merge'
51 SVN_PROP_BZR_REVISION_INFO = 'bzr:revision-info'
52 SVN_REVPROP_BZR_SIGNATURE = 'bzr:gpg-signature'
53 SVN_PROP_BZR_REVISION_ID = 'bzr:revision-id-v%d:' % MAPPING_VERSION
54 SVN_PROP_BZR_BRANCHING_SCHEME = 'bzr:branching-scheme'
56 def parse_revid_property(line):
57 """Parse a (revnum, revid) tuple as set in revision id properties.
58 :param line: line to parse
59 :return: tuple with (bzr_revno, revid)
61 assert not '\n' in line
63 (revno, revid) = line.split(' ', 1)
65 raise errors.InvalidPropertyValue(SVN_PROP_BZR_REVISION_ID,
68 raise errors.InvalidPropertyValue(SVN_PROP_BZR_REVISION_ID,
70 return (int(revno), revid)
73 def parse_revision_metadata(text, rev):
74 """Parse a revision info text (as set in bzr:revision-info).
76 :param text: text to parse
77 :param rev: Revision object to apply read parameters to
80 for l in text.splitlines():
82 key, value = l.split(": ", 2)
84 raise errors.InvalidPropertyValue(SVN_PROP_BZR_REVISION_INFO,
85 "Missing : in revision metadata")
86 if key == "committer":
87 rev.committer = str(value)
88 elif key == "timestamp":
89 (rev.timestamp, rev.timezone) = unpack_highres_date(value)
90 elif key == "properties":
92 elif key[0] == "\t" and in_properties:
93 rev.properties[str(key[1:])] = str(value)
95 raise errors.InvalidPropertyValue(SVN_PROP_BZR_REVISION_INFO,
96 "Invalid key %r" % key)
99 def generate_revision_metadata(timestamp, timezone, committer, revprops):
100 """Generate revision metadata text for the specified revision
103 :param timestamp: timestamp of the revision, in seconds since epoch
104 :param timezone: timezone, specified by offset from GMT in seconds
105 :param committer: name/email of the committer
106 :param revprops: dictionary with custom revision properties
107 :return: text with data to set bzr:revision-info to.
109 assert timestamp is None or isinstance(timestamp, float)
111 if timestamp is not None:
112 text += "timestamp: %s\n" % format_highres_date(timestamp, timezone)
113 if committer is not None:
114 text += "committer: %s\n" % committer
115 if revprops is not None and revprops != {}:
116 text += "properties: \n"
117 for k, v in sorted(revprops.items()):
118 text += "\t%s: %s\n" % (k, v)
122 def svk_feature_to_revision_id(feature, scheme):
123 """Create a revision id from a svk feature identifier.
125 :param feature: The feature identifier as string.
126 :param scheme: Branching scheme name
127 :return: Matching revision id.
129 (uuid, branch, revnum) = feature.split(":")
130 return generate_svn_revision_id(uuid, int(revnum), branch.strip("/"),
134 def revision_id_to_svk_feature(revid):
135 """Create a SVK feature identifier from a revision id.
137 :param revid: Revision id to convert.
138 :return: Matching SVK feature identifier.
140 (uuid, branch, revnum, scheme) = parse_svn_revision_id(revid)
141 return "%s:/%s:%d" % (uuid, branch, revnum)
144 class SvnRepositoryFormat(RepositoryFormat):
145 """Repository format for Subversion repositories (accessed using svn_ra).
147 rich_root_data = True
150 super(SvnRepositoryFormat, self).__init__()
152 def get_format_description(self):
153 return "Subversion Repository"
155 def initialize(self, url, shared=False, _internal=False):
156 """Svn repositories cannot be created (yet)."""
157 raise UninitializableFormat(self)
159 def check_conversion_target(self, target_repo_format):
160 return target_repo_format.rich_root_data
164 class SvnRepository(Repository):
166 Provides a simplified interface to a Subversion repository
167 by using the RA (remote access) API from subversion
169 def __init__(self, bzrdir, transport, guessed_scheme):
170 from fileids import SimpleFileIdMap
171 _revision_store = None
173 assert isinstance(transport, Transport)
175 control_files = LockableFiles(transport, '', TransportLock)
176 Repository.__init__(self, SvnRepositoryFormat(), bzrdir,
177 control_files, None, None, None)
179 self.transport = transport
180 self.uuid = transport.get_uuid()
181 assert self.uuid is not None
182 self.base = transport.base
183 assert self.base is not None
184 self._serializer = None
187 self.config = SvnRepositoryConfig(self.uuid)
188 self.config.add_location(self.base)
189 cache_file = os.path.join(self.create_cache_dir(),
190 'cache-v%d' % MAPPING_VERSION)
191 if not cachedbs.has_key(cache_file):
192 cachedbs[cache_file] = sqlite3.connect(cache_file)
193 self.cachedb = cachedbs[cache_file]
195 self._latest_revnum = transport.get_latest_revnum()
196 self._log = logwalker.LogWalker(transport=transport,
197 cache_db=self.cachedb,
198 last_revnum=self._latest_revnum)
200 self.branchprop_list = BranchPropertyList(self._log, self.cachedb)
201 self.fileid_map = SimpleFileIdMap(self, self.cachedb)
202 self.revmap = RevidMap(self.cachedb)
203 if self.config.get_branching_scheme() is not None:
204 self.scheme = self.config.get_branching_scheme()
206 text = self.branchprop_list.get_property("", self._latest_revnum,
207 SVN_PROP_BZR_BRANCHING_SCHEME, None)
209 self.set_branching_scheme(
210 ListBranchingScheme(parse_list_scheme_text(text)))
212 self.scheme = guessed_scheme
213 assert self.scheme is not None
215 def set_branching_scheme(self, scheme):
217 self.config.set_branching_scheme(str(scheme))
219 def _warn_if_deprecated(self):
220 # This class isn't deprecated
224 return '%s(%r)' % (self.__class__.__name__,
227 def create_cache_dir(self):
228 cache_dir = create_cache_dir()
229 dir = os.path.join(cache_dir, self.uuid)
230 if not os.path.exists(dir):
234 def _check(self, revision_ids):
235 return BranchCheckResult(self)
237 def get_inventory(self, revision_id):
238 assert revision_id != None
239 return self.revision_tree(revision_id).inventory
241 def get_fileid_map(self, revnum, path, scheme):
242 return self.fileid_map.get_map(self.uuid, revnum, path,
243 self.revision_fileid_renames, scheme)
245 def transform_fileid_map(self, uuid, revnum, branch, changes, renames,
247 return self.fileid_map.apply_changes(uuid, revnum, branch, changes,
250 def all_revision_ids(self, scheme=None):
253 for (bp, rev) in self.follow_history(
254 self.transport.get_latest_revnum(), scheme):
255 yield self.generate_revision_id(rev, bp, str(scheme))
257 def get_inventory_weave(self):
258 raise NotImplementedError(self.get_inventory_weave)
260 def set_make_working_trees(self, new_value):
261 """See Repository.set_make_working_trees()."""
262 pass # FIXME: ignored, nowhere to store it...
264 def make_working_trees(self):
265 """See Repository.make_working_trees().
267 Always returns False, as working trees are never created inside
268 Subversion repositories.
272 def get_ancestry(self, revision_id):
273 """See Repository.get_ancestry().
275 Note: only the first bit is topologically ordered!
277 if revision_id is None:
280 (path, revnum, scheme) = self.lookup_revision_id(revision_id)
282 ancestry = [revision_id]
284 for l in self.branchprop_list.get_property(path, revnum,
285 SVN_PROP_BZR_MERGE, "").splitlines():
286 ancestry.extend(l.split("\n"))
289 for (branch, rev) in self.follow_branch(path, revnum - 1, scheme):
290 ancestry.append(self.generate_revision_id(rev, branch, scheme))
292 ancestry.append(None)
296 def has_revision(self, revision_id):
297 """See Repository.has_revision()."""
298 if revision_id is None:
302 (path, revnum, scheme) = self.lookup_revision_id(revision_id)
303 except NoSuchRevision:
307 return (svn.core.svn_node_dir == self.transport.check_path(path, revnum))
308 except SubversionException, (_, num):
309 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
314 def revision_trees(self, revids):
315 """See Repository.revision_trees()."""
317 yield self.revision_tree(revid)
319 def revision_tree(self, revision_id):
320 """See Repository.revision_tree()."""
321 if revision_id is None:
322 revision_id = NULL_REVISION
324 if revision_id == NULL_REVISION:
325 inventory = Inventory(root_id=None)
326 inventory.revision_id = revision_id
327 return RevisionTree(self, inventory, revision_id)
329 return SvnRevisionTree(self, revision_id)
331 def revision_fileid_renames(self, revid):
332 """Check which files were renamed in a particular revision.
334 :param revid: Id of revision to look up.
335 :return: dictionary with paths as keys, file ids as values
337 (path, revnum, scheme) = self.lookup_revision_id(revid)
339 for line in self.branchprop_list.get_property_diff(path, revnum,
340 SVN_PROP_BZR_FILEIDS).splitlines():
341 (path, key) = line.split("\t", 2)
342 ret[path] = osutils.safe_file_id(key)
345 def _mainline_revision_parent(self, path, revnum, scheme):
346 """Find the mainline parent of the specified revision.
348 :param path: Path of the revision in Subversion
349 :param revnum: Subversion revision number
350 :param scheme: Name of branching scheme to use
351 :return: Revision id of the left-hand-side parent or None if
352 this is the first revision
354 assert isinstance(path, basestring)
355 assert isinstance(revnum, int)
357 if not scheme.is_branch(path) and \
358 not scheme.is_tag(path):
359 raise NoSuchRevision(self,
360 self.generate_revision_id(revnum, path, scheme))
362 it = self.follow_branch(path, revnum, scheme)
363 # the first tuple returned should match the one specified.
364 # if it's not, then the branch, revnum didn't change in the specified
365 # revision and so it is invalid
366 if (path, revnum) != it.next():
367 raise NoSuchRevision(self,
368 self.generate_revision_id(revnum, path, scheme))
370 (branch, rev) = it.next()
371 return self.generate_revision_id(rev, branch, scheme)
372 except StopIteration:
373 # The specified revision was the first one in the branch
376 def revision_parents(self, revision_id, merged_data=None):
378 (branch, revnum, scheme) = self.lookup_revision_id(revision_id)
379 mainline_parent = self._mainline_revision_parent(branch, revnum, scheme)
380 if mainline_parent is not None:
381 parent_ids.append(mainline_parent)
382 (parent_path, parent_revnum, scheme) = self.lookup_revision_id(mainline_parent)
386 # if the branch didn't change, bzr:merge can't have changed
387 if not self._log.touches_path(branch, revnum):
390 if merged_data is None:
391 new_merge = self.branchprop_list.get_property(branch, revnum,
392 SVN_PROP_BZR_MERGE, "").splitlines()
394 if len(new_merge) == 0 or parent_path is None:
397 old_merge = self.branchprop_list.get_property(parent_path, parent_revnum,
398 SVN_PROP_BZR_MERGE, "").splitlines()
400 assert (len(old_merge) == len(new_merge) or
401 len(old_merge) + 1 == len(new_merge))
403 if len(old_merge) < len(new_merge):
404 merged_data = new_merge[-1]
408 if ' ' in merged_data:
409 mutter('invalid revision id %r in merged property, skipping' % merged_data)
412 if merged_data != "":
413 parent_ids.extend(merged_data.split("\t"))
417 def get_revision(self, revision_id):
418 """See Repository.get_revision."""
419 if not revision_id or not isinstance(revision_id, basestring):
420 raise InvalidRevisionId(revision_id=revision_id, branch=self)
422 (path, revnum, scheme) = self.lookup_revision_id(revision_id)
424 parent_ids = self.revision_parents(revision_id)
426 # Commit SVN revision properties to a Revision object
427 rev = Revision(revision_id=revision_id, parent_ids=parent_ids)
429 (rev.committer, rev.message, date) = self._log.get_revision_info(revnum)
430 if rev.committer is None:
434 rev.timestamp = 1.0 * svn.core.secs_from_timestr(date, None)
436 rev.timestamp = 0.0 # FIXME: Obtain repository creation time
439 parse_revision_metadata(
440 self.branchprop_list.get_property(path, revnum,
441 SVN_PROP_BZR_REVISION_INFO, ""), rev)
443 rev.inventory_sha1 = property(lambda: self.get_inventory_sha1(revision_id))
447 def get_revisions(self, revision_ids):
448 # TODO: More efficient implementation?
449 return map(self.get_revision, revision_ids)
451 def add_revision(self, rev_id, rev, inv=None, config=None):
452 raise NotImplementedError(self.add_revision)
454 def generate_revision_id(self, revnum, path, scheme):
455 """Generate an unambiguous revision id.
457 :param revnum: Subversion revision number.
458 :param path: Branch path.
459 :param scheme: Branching scheme name
461 :return: New revision id.
463 assert isinstance(path, str)
464 assert isinstance(revnum, int)
466 # Look in the cache to see if it already has a revision id
467 revid = self.revmap.lookup_branch_revnum(revnum, path, scheme)
468 if revid is not None:
471 # Lookup the revision from the bzr:revision-id-vX property
472 line = self.branchprop_list.get_property_diff(path, revnum,
473 SVN_PROP_BZR_REVISION_ID+str(scheme)).strip("\n")
476 revid = generate_svn_revision_id(self.uuid, revnum, path,
480 (bzr_revno, revid) = parse_revid_property(line)
481 self.revmap.insert_revid(revid, path, revnum, revnum,
483 except errors.InvalidPropertyValue, e:
485 revid = generate_svn_revision_id(self.uuid, revnum, path,
487 self.revmap.insert_revid(revid, path, revnum, revnum,
492 def lookup_revision_id(self, revid, scheme=None):
493 """Parse an existing Subversion-based revision id.
495 :param revid: The revision id.
496 :param scheme: Optional branching scheme to use when searching for
498 :raises: NoSuchRevision
499 :return: Tuple with branch path, revision number and scheme.
501 def get_scheme(name):
502 assert isinstance(name, basestring)
503 return BranchingScheme.find_scheme(name)
507 (uuid, branch_path, revnum, schemen) = parse_svn_revision_id(revid)
508 assert isinstance(branch_path, str)
509 if uuid == self.uuid:
510 return (branch_path, revnum, get_scheme(schemen))
511 # If the UUID doesn't match, this may still be a valid revision
512 # id; a revision from another SVN repository may be pushed into
514 except InvalidRevisionId:
517 # Check the record out of the revmap, if it exists
519 (branch_path, min_revnum, max_revnum, \
520 scheme) = self.revmap.lookup_revid(revid)
521 assert isinstance(branch_path, str)
522 # Entry already complete?
523 if min_revnum == max_revnum:
524 return (branch_path, min_revnum, get_scheme(scheme))
525 except NoSuchRevision:
526 # If there is no entry in the map, walk over all branches:
529 for (branch, revno, exists) in self.find_branches(scheme):
530 # Look at their bzr:revision-id-vX
532 for line in self.branchprop_list.get_property(branch, revno,
533 SVN_PROP_BZR_REVISION_ID+str(scheme), "").splitlines():
535 revids.append(parse_revid_property(line))
536 except errors.InvalidPropertyValue, e:
539 # If there are any new entries that are not yet in the cache,
541 for (entry_revno, entry_revid) in revids:
542 self.revmap.insert_revid(entry_revid, branch, 0, revno,
543 str(scheme), entry_revno)
548 (branch_path, min_revnum, max_revnum, scheme) = self.revmap.lookup_revid(revid)
549 assert isinstance(branch_path, str)
551 # Find the branch property between min_revnum and max_revnum that
554 for (bp, rev) in self.follow_branch(branch_path, max_revnum,
557 (entry_revno, entry_revid) = parse_revid_property(
558 self.branchprop_list.get_property_diff(bp, rev,
559 SVN_PROP_BZR_REVISION_ID+str(scheme)).strip("\n"))
560 except errors.InvalidPropertyValue:
561 # Don't warn about encountering an invalid property,
562 # that will already have happened earlier
564 if entry_revid == revid:
565 self.revmap.insert_revid(revid, bp, rev, rev, scheme,
567 return (bp, rev, get_scheme(scheme))
569 raise AssertionError("Revision id %s was added incorrectly" % revid)
571 def get_inventory_xml(self, revision_id):
572 return bzrlib.xml5.serializer_v5.write_inventory_to_string(
573 self.get_inventory(revision_id))
575 def get_inventory_sha1(self, revision_id):
576 """Get the sha1 for the XML representation of an inventory.
578 :param revision_id: Revision id of the inventory for which to return
583 return osutils.sha_string(self.get_inventory_xml(revision_id))
585 def get_revision_xml(self, revision_id):
586 """Return the XML representation of a revision.
588 :param revision_id: Revision for which to return the XML.
591 return bzrlib.xml5.serializer_v5.write_revision_to_string(
592 self.get_revision(revision_id))
594 def follow_history(self, revnum, scheme):
595 """Yield all the branches found between the start of history
596 and a specified revision number.
598 :param revnum: Revision number up to which to search.
599 :return: iterator over branches in the range 0..revnum
601 assert scheme is not None
605 paths = self._log.get_revision_paths(revnum)
608 bp = scheme.unprefix(p)[0]
609 if not bp in yielded_paths:
610 if not paths.has_key(bp) or paths[bp][0] != 'D':
611 assert revnum > 0 or bp == ""
613 yielded_paths.append(bp)
614 except NotBranchError:
618 def follow_branch(self, branch_path, revnum, scheme):
619 """Follow the history of a branch. Will yield all the
620 left-hand side ancestors of a specified revision.
622 :param branch_path: Subversion path to search.
623 :param revnum: Revision number in Subversion to start.
624 :param scheme: Name of the branching scheme to use
625 :return: iterator over the ancestors
627 assert branch_path is not None
628 assert isinstance(branch_path, str)
629 assert isinstance(revnum, int) and revnum >= 0
630 if not scheme.is_branch(branch_path) and \
631 not scheme.is_tag(branch_path):
632 raise errors.NotSvnBranchPath(branch_path, revnum)
633 branch_path = branch_path.strip("/")
636 paths = self._log.get_revision_paths(revnum)
639 # If something underneath branch_path changed, there is a
640 # revision there, so yield it.
642 assert isinstance(p, str)
643 if p == branch_path or p.startswith(branch_path+"/") or branch_path == "":
644 yield (branch_path, revnum)
648 # If there are no special cases, just go try the
649 # next revnum in history
652 # Make sure we get the right location for next time, if
653 # the branch itself was copied
654 if (paths.has_key(branch_path) and
655 paths[branch_path][0] in ('R', 'A')):
657 yield (branch_path, revnum+1)
658 if paths[branch_path][1] is None:
660 if not scheme.is_branch(paths[branch_path][1]) and \
661 not scheme.is_tag(paths[branch_path][1]):
662 # FIXME: if copyfrom_path is not a branch path,
663 # should simulate a reverse "split" of a branch
664 # for now, just make it look like the branch ended here
666 revnum = paths[branch_path][2]
667 branch_path = paths[branch_path][1].encode("utf-8")
670 # Make sure we get the right location for the next time if
671 # one of the parents changed
673 # Path names need to be sorted so the longer paths
674 # override the shorter ones
675 path_names = paths.keys()
678 if branch_path.startswith(p+"/"):
679 assert paths[p][1] is not None and paths[p][0] in ('A', 'R'), "Parent didn't exist yet, but child wasn't added !?"
682 branch_path = paths[p][1].encode("utf-8") + branch_path[len(p):]
684 """Return all the changes that happened in a branch
685 between branch_path and revnum.
687 :return: iterator that returns tuples with branch path,
688 changed paths and revision number.
690 def follow_branch_history(self, branch_path, revnum, scheme):
691 assert branch_path is not None
692 if not scheme.is_branch(branch_path) and \
693 not scheme.is_tag(branch_path):
694 raise errors.NotSvnBranchPath(branch_path, revnum)
696 for (bp, paths, revnum) in self._log.follow_path(branch_path, revnum):
697 if (paths.has_key(bp) and
698 paths[bp][1] is not None and
699 not scheme.is_branch(paths[bp][1]) and
700 not scheme.is_tag(paths[bp][1])):
701 # FIXME: if copyfrom_path is not a branch path,
702 # should simulate a reverse "split" of a branch
703 # for now, just make it look like the branch ended here
704 for c in self._log.find_children(paths[bp][1], paths[bp][2]):
705 path = c.replace(paths[bp][1], bp+"/", 1).replace("//", "/")
706 paths[path] = ('A', None, -1)
707 paths[bp] = ('A', None, -1)
709 yield (bp, paths, revnum)
712 yield (bp, paths, revnum)
714 """Check whether a signature exists for a particular revision id.
716 :param revision_id: Revision id for which the signatures should be looked up.
717 :return: False, as no signatures are stored for revisions in Subversion
720 def has_signature_for_revision_id(self, revision_id):
721 # TODO: Retrieve from SVN_PROP_BZR_SIGNATURE
722 return False # SVN doesn't store GPG signatures. Perhaps
723 # store in SVN revision property?
726 def get_signature_text(self, revision_id):
727 """Return the signature text for a particular revision.
729 :param revision_id: Id of the revision for which to return the
731 :raises NoSuchRevision: Always
733 # TODO: Retrieve from SVN_PROP_BZR_SIGNATURE
734 # SVN doesn't store GPG signatures
735 raise NoSuchRevision(self, revision_id)
737 def _full_revision_graph(self, scheme):
739 for (branch, revnum) in self.follow_history(self._latest_revnum,
741 mutter('%r, %r' % (branch, revnum))
742 revid = self.generate_revision_id(revnum, branch, scheme)
743 graph[revid] = self.revision_parents(revid)
746 def get_revision_graph(self, revision_id=None):
747 if revision_id == NULL_REVISION:
750 if revision_id is None:
751 return self._full_revision_graph(self.scheme)
753 (path, revnum, scheme) = self.lookup_revision_id(revision_id)
755 _previous = revision_id
759 for (branch, rev) in self.follow_branch(path, revnum - 1, scheme):
760 revid = self.generate_revision_id(rev, branch, scheme)
761 self._ancestry[_previous] = [revid]
764 self._ancestry[_previous] = []
766 return self._ancestry
768 def find_branches(self, scheme, revnum=None, pb=None):
769 """Find all branches that were changed in the specified revision number.
771 :param revnum: Revision to search for branches.
772 :return: iterator that returns tuples with (path, revision number, still exists). The revision number is the revision in which the branch last existed.
774 assert scheme is not None
776 revnum = self.transport.get_latest_revnum()
778 created_branches = {}
780 for i in range(revnum+1):
782 pb.update("finding branches", i, revnum+1)
783 paths = self._log.get_revision_paths(i)
787 if scheme.is_branch(p) or scheme.is_tag(p):
788 if paths[p][0] in ('R', 'D'):
789 del created_branches[p]
790 j = self._log.find_latest_change(p, i-1, recurse=True)
793 if paths[p][0] in ('A', 'R'):
794 created_branches[p] = i
795 elif scheme.is_branch_parent(p) or \
796 scheme.is_tag_parent(p):
797 if paths[p][0] in ('R', 'D'):
798 k = created_branches.keys()
800 if c.startswith(p+"/"):
801 del created_branches[c]
802 j = self._log.find_latest_change(c, i-1,
805 if paths[p][0] in ('A', 'R'):
809 for c in self.transport.get_dir(p, i)[0].keys():
811 if scheme.is_branch(n) or scheme.is_tag(n):
812 created_branches[n] = i
813 elif scheme.is_branch_parent(n) or scheme.is_tag_parent(n):
816 for p in created_branches:
817 j = self._log.find_latest_change(p, revnum, recurse=True)
819 j = created_branches[p]
823 """Return True if this repository is flagged as a shared repository."""
826 def get_physical_lock_status(self):
829 def get_commit_builder(self, branch, parents, config, timestamp=None,
830 timezone=None, committer=None, revprops=None,
832 from commit import SvnCommitBuilder
833 return SvnCommitBuilder(self, branch, parents, config, timestamp,
834 timezone, committer, revprops, revision_id)