1 # Copyright (C) 2006-2008 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 3 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, ui, urlutils, xml5
20 from bzrlib.branch import Branch, BranchCheckResult
21 from bzrlib.errors import (InvalidRevisionId, NoSuchRevision, NotBranchError,
22 UninitializableFormat, UnrelatedBranches)
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, get_transport
29 from bzrlib.trace import info, mutter
31 from svn.core import SubversionException, Pool
36 from branchprops import PathPropertyProvider
37 from cache import create_cache_dir, sqlite3
38 from config import SvnRepositoryConfig
40 from graph import Graph, CachingParentsProvider
42 from mapping import (SVN_PROP_BZR_REVISION_ID, SVN_REVPROP_BZR_SIGNATURE,
43 SVN_PROP_BZR_BRANCHING_SCHEME, BzrSvnMappingv3FileProps,
44 parse_revision_metadata, parse_revid_property,
45 parse_merge_property, BzrSvnMapping,
46 get_default_mapping, parse_revision_id)
47 from revids import RevidMap
48 from scheme import (BranchingScheme, ListBranchingScheme,
49 parse_list_scheme_text, guess_scheme_from_history)
50 from svk import (SVN_PROP_SVK_MERGE, svk_features_merged_since,
52 from tree import SvnRevisionTree
56 def __init__(self, create_fn):
57 self.create_fn = create_fn
60 def _ensure_init(self):
62 self.dict = self.create_fn()
68 def __getitem__(self, key):
72 def __setitem__(self, key, value):
74 self.dict[key] = value
76 def get(self, key, default=None):
78 return self.dict.get(key, default)
80 def has_key(self, key):
82 return self.dict.has_key(key)
85 def svk_feature_to_revision_id(feature, mapping):
86 """Convert a SVK feature to a revision id for this repository.
88 :param feature: SVK feature.
92 (uuid, bp, revnum) = parse_svk_feature(feature)
93 except errors.InvalidPropertyValue:
95 if not mapping.is_branch(bp) and not mapping.is_tag(bp):
97 return mapping.generate_revision_id(uuid, revnum, bp)
100 class SvnRepositoryFormat(RepositoryFormat):
101 """Repository format for Subversion repositories (accessed using svn_ra).
103 rich_root_data = True
105 def __get_matchingbzrdir(self):
106 from remote import SvnRemoteFormat
107 return SvnRemoteFormat()
109 _matchingbzrdir = property(__get_matchingbzrdir)
112 super(SvnRepositoryFormat, self).__init__()
114 def get_format_description(self):
115 return "Subversion Repository"
117 def initialize(self, url, shared=False, _internal=False):
118 raise UninitializableFormat(self)
120 def check_conversion_target(self, target_repo_format):
121 return target_repo_format.rich_root_data
124 def changes_path(changes, path):
125 """Check if one of the specified changes applies
126 to path or one of its children.
129 assert isinstance(p, str)
130 if p == path or p.startswith(path+"/") or path == "":
140 class SvnRepository(Repository):
142 Provides a simplified interface to a Subversion repository
143 by using the RA (remote access) API from subversion
145 def __init__(self, bzrdir, transport, branch_path=None):
146 from bzrlib.plugins.svn import lazy_register_optimizers
147 lazy_register_optimizers()
148 from fileids import SimpleFileIdMap
149 _revision_store = None
151 assert isinstance(transport, Transport)
153 control_files = LockableFiles(transport, '', TransportLock)
154 Repository.__init__(self, SvnRepositoryFormat(), bzrdir,
155 control_files, None, None, None)
157 self.transport = transport
158 self.uuid = transport.get_uuid()
159 assert self.uuid is not None
160 self.base = transport.base
161 assert self.base is not None
162 self._serializer = xml5.serializer_v5
165 self.get_config().add_location(self.base)
166 cache_dir = self.create_cache_dir()
167 cachedir_transport = get_transport(cache_dir)
168 cache_file = os.path.join(cache_dir, 'cache-v%d' % CACHE_DB_VERSION)
169 if not cachedbs.has_key(cache_file):
170 cachedbs[cache_file] = sqlite3.connect(cache_file)
171 self.cachedb = cachedbs[cache_file]
173 self._log = logwalker.LogWalker(transport=transport,
174 cache_db=self.cachedb)
176 # TODO: Only use fileid_map when
177 # fileprops-based mappings are being used
178 self.branchprop_list = PathPropertyProvider(self._log)
179 self.fileid_map = SimpleFileIdMap(self, cachedir_transport)
180 self.revmap = RevidMap(self.cachedb)
182 self._hinted_branch_path = branch_path
184 def lhs_missing_revisions(self, revhistory, stop_revision):
186 slice = revhistory[:revhistory.index(stop_revision)+1]
187 for revid in reversed(slice):
188 if self.has_revision(revid):
191 missing.append(revid)
192 raise UnrelatedBranches()
194 def get_transaction(self):
195 raise NotImplementedError(self.get_transaction)
197 def get_stored_scheme(self):
198 """Retrieve the stored branching scheme, either in the repository
199 or in the configuration file.
201 scheme = self.get_config().get_branching_scheme()
202 if scheme is not None:
203 return (scheme, self.get_config().branching_scheme_is_mandatory())
205 last_revnum = self.transport.get_latest_revnum()
206 scheme = self._get_property_scheme(last_revnum)
207 if scheme is not None:
208 return (scheme, True)
212 def get_mapping(self):
213 return get_default_mapping()(self.get_scheme())
215 def _make_parents_provider(self):
216 return CachingParentsProvider(self)
218 def get_scheme(self):
219 """Determine the branching scheme to use for this repository.
221 :return: Branching scheme.
223 # First, try to use the branching scheme we already know
224 if self._scheme is not None:
227 (scheme, mandatory) = self.get_stored_scheme()
229 self._scheme = scheme
232 if scheme is not None:
233 if (self._hinted_branch_path is None or
234 scheme.is_branch(self._hinted_branch_path)):
235 self._scheme = scheme
238 last_revnum = self.transport.get_latest_revnum()
239 self.set_branching_scheme(
240 self._guess_scheme(last_revnum, self._hinted_branch_path),
241 store=(last_revnum > 20),
246 def _get_property_scheme(self, revnum=None):
248 revnum = self.transport.get_latest_revnum()
249 text = self.branchprop_list.get_properties("", revnum).get(SVN_PROP_BZR_BRANCHING_SCHEME, None)
252 return ListBranchingScheme(parse_list_scheme_text(text))
254 def set_property_scheme(self, scheme):
255 def done(revmetadata, pool):
257 editor = self.transport.get_commit_editor(
258 {svn.core.SVN_PROP_REVISION_LOG: "Updating branching scheme for Bazaar."},
260 root = editor.open_root(-1)
261 editor.change_dir_prop(root, SVN_PROP_BZR_BRANCHING_SCHEME,
262 "".join(map(lambda x: x+"\n", scheme.branch_list)).encode("utf-8"))
263 editor.close_directory(root)
266 def _guess_scheme(self, last_revnum, branch_path=None):
267 scheme = guess_scheme_from_history(
268 self._log.iter_changes("", last_revnum), last_revnum,
270 mutter("Guessed branching scheme: %r" % scheme)
273 def set_branching_scheme(self, scheme, store=True, mandatory=False):
274 self._scheme = scheme
276 self.get_config().set_branching_scheme(str(scheme),
279 def _warn_if_deprecated(self):
280 # This class isn't deprecated
284 return '%s(%r)' % (self.__class__.__name__,
287 def create_cache_dir(self):
288 cache_dir = create_cache_dir()
289 dir = os.path.join(cache_dir, self.uuid)
290 if not os.path.exists(dir):
291 info("Initialising Subversion metadata cache in %s" % dir)
295 def _check(self, revision_ids):
296 return BranchCheckResult(self)
298 def get_inventory(self, revision_id):
299 assert revision_id != None
300 return self.revision_tree(revision_id).inventory
302 def get_fileid_map(self, revnum, path, mapping):
303 return self.fileid_map.get_map(self.uuid, revnum, path,
304 self.revision_fileid_renames, mapping)
306 def transform_fileid_map(self, uuid, revnum, branch, changes, renames,
308 return self.fileid_map.apply_changes(uuid, revnum, branch, changes,
311 def all_revision_ids(self, mapping=None):
313 mapping = self.get_mapping()
314 for (bp, rev) in self.follow_history(
315 self.transport.get_latest_revnum(), mapping):
316 yield self.generate_revision_id(rev, bp, mapping)
318 def get_inventory_weave(self):
319 """See Repository.get_inventory_weave()."""
320 raise NotImplementedError(self.get_inventory_weave)
322 def set_make_working_trees(self, new_value):
323 """See Repository.set_make_working_trees()."""
324 pass # FIXME: ignored, nowhere to store it...
326 def make_working_trees(self):
327 """See Repository.make_working_trees().
329 Always returns False, as working trees are never created inside
330 Subversion repositories.
334 def iter_changes(self):
337 :return: iterator over tuples with (revid, parent_revids, changes, revprops, branchprops)
339 raise NotImplementedError
341 def iter_reverse_revision_history(self, revision_id):
342 """Iterate backwards through revision ids in the lefthand history
344 :param revision_id: The revision id to start with. All its lefthand
345 ancestors will be traversed.
347 if revision_id in (None, NULL_REVISION):
349 for (revid, parent_revid) in self.get_graph().iter_lhs_ancestry(revision_id):
352 def get_graph(self, other_repository=None):
353 """Return the graph walker for this repository format"""
354 parents_provider = self._make_parents_provider()
355 return Graph(parents_provider)
357 def get_ancestry(self, revision_id, topo_sorted=True):
358 """See Repository.get_ancestry().
361 graph = self.get_graph()
362 for rev, parents in graph.iter_ancestry([revision_id]):
363 if rev == NULL_REVISION:
369 def has_revision(self, revision_id):
370 """See Repository.has_revision()."""
371 if revision_id is None:
375 (path, revnum, _) = self.lookup_revision_id(revision_id)
376 except NoSuchRevision:
380 return (svn.core.svn_node_dir == self.transport.check_path(path, revnum))
381 except SubversionException, (_, num):
382 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
386 def revision_trees(self, revids):
387 """See Repository.revision_trees()."""
389 yield self.revision_tree(revid)
391 def revision_tree(self, revision_id):
392 """See Repository.revision_tree()."""
393 if revision_id is None:
394 revision_id = NULL_REVISION
396 if revision_id == NULL_REVISION:
397 inventory = Inventory(root_id=None)
398 inventory.revision_id = revision_id
399 return RevisionTree(self, inventory, revision_id)
401 return SvnRevisionTree(self, revision_id)
403 def revision_fileid_renames(self, path, revnum, mapping,
404 revprops=None, fileprops=None):
405 """Check which files were renamed in a particular revision.
407 :param path: Branch path
409 :return: dictionary with paths as keys, file ids as values
412 revprops = lazy_dict(lambda: self._log.transport.revprop_list(revnum))
413 if fileprops is None:
414 fileprops = lazy_dict(lambda: self.branchprop_list.get_changed_properties(path, revnum))
416 return mapping.import_fileid_map(revprops, fileprops)
418 def lhs_revision_parent(self, path, revnum, mapping):
419 """Find the mainline parent of the specified revision.
421 :param path: Path of the revision in Subversion
422 :param revnum: Subversion revision number
423 :param mapping: Mapping.
424 :return: Revision id of the left-hand-side parent or None if
425 this is the first revision
427 assert isinstance(path, str)
428 assert isinstance(revnum, int)
430 if not mapping.is_branch(path) and \
431 not mapping.is_tag(path):
432 raise NoSuchRevision(self,
433 self.generate_revision_id(revnum, path, mapping))
435 # Make sure the specified revision actually exists
436 changes = self._log.get_revision_paths(revnum)
437 if not changes_path(changes, path):
438 # the specified revno should be changing the branch or
439 # otherwise it is invalid
440 raise NoSuchRevision(self,
441 self.generate_revision_id(revnum, path, mapping))
444 next = logwalker.changes_find_prev_location(changes, path, revnum)
447 (path, revnum) = next
448 changes = self._log.get_revision_paths(revnum)
450 if changes_path(changes, path):
451 revid = self.generate_revision_id(revnum, path, mapping)
452 if not mapping.is_branch(path) and \
453 not mapping.is_tag(path):
458 def get_parent_map(self, revids):
460 for revision_id in revids:
461 if revision_id == NULL_REVISION:
462 parent_map[revision_id] = ()
465 parents = self.revision_parents(revision_id)
466 except NoSuchRevision:
469 if len(parents) == 0:
470 parents = (NULL_REVISION,)
471 parent_map[revision_id] = parents
474 def _svk_merged_revisions(self, branch, revnum, mapping,
476 """Find out what SVK features were merged in a revision.
479 current = fileprops.get(SVN_PROP_SVK_MERGE, "")
482 (prev_path, prev_revnum) = self._log.get_previous(branch, revnum)
483 if prev_path is None and prev_revnum == -1:
486 previous = self.branchprop_list.get_properties(prev_path.encode("utf-8"), prev_revnum).get(SVN_PROP_SVK_MERGE, "")
487 for feature in svk_features_merged_since(current, previous):
488 revid = svk_feature_to_revision_id(feature, mapping)
489 if revid is not None:
492 def revision_parents(self, revision_id, svn_fileprops=None, svn_revprops=None):
493 """See Repository.revision_parents()."""
494 (branch, revnum, mapping) = self.lookup_revision_id(revision_id)
495 mainline_parent = self.lhs_revision_parent(branch, revnum, mapping)
496 if mainline_parent is None:
499 parent_ids = (mainline_parent,)
501 if svn_fileprops is None:
502 svn_fileprops = lazy_dict(lambda: self.branchprop_list.get_changed_properties(branch, revnum))
504 if svn_revprops is None:
505 svn_revprops = lazy_dict(lambda: self.transport.revprop_list(revnum))
507 extra_rhs_parents = mapping.get_rhs_parents(branch, svn_revprops, svn_fileprops)
508 parent_ids += extra_rhs_parents
510 if extra_rhs_parents == ():
511 parent_ids += tuple(self._svk_merged_revisions(branch, revnum, mapping, svn_fileprops))
515 def get_revision(self, revision_id, svn_revprops=None, svn_fileprops=None):
516 """See Repository.get_revision."""
517 if not revision_id or not isinstance(revision_id, str):
518 raise InvalidRevisionId(revision_id=revision_id, branch=self)
520 (path, revnum, mapping) = self.lookup_revision_id(revision_id)
522 if svn_revprops is None:
523 svn_revprops = lazy_dict(lambda: self.transport.revprop_list(revnum))
524 if svn_fileprops is None:
525 svn_fileprops = lazy_dict(lambda: self.branchprop_list.get_changed_properties(path, revnum))
526 parent_ids = self.revision_parents(revision_id, svn_fileprops=svn_fileprops, svn_revprops=svn_revprops)
528 rev = Revision(revision_id=revision_id, parent_ids=parent_ids,
531 mapping.import_revision(svn_revprops, svn_fileprops, rev)
535 def get_revisions(self, revision_ids):
536 """See Repository.get_revisions()."""
537 # TODO: More efficient implementation?
538 return map(self.get_revision, revision_ids)
540 def add_revision(self, rev_id, rev, inv=None, config=None):
541 raise NotImplementedError(self.add_revision)
543 def generate_revision_id(self, revnum, path, mapping):
544 """Generate an unambiguous revision id.
546 :param revnum: Subversion revision number.
547 :param path: Branch path.
548 :param mapping: Mapping to use.
550 :return: New revision id.
552 assert isinstance(path, str)
553 assert isinstance(revnum, int)
554 assert isinstance(mapping, BzrSvnMapping)
556 # Look in the cache to see if it already has a revision id
557 revid = self.revmap.lookup_branch_revnum(revnum, path, str(mapping.scheme))
558 if revid is not None:
561 # See if there is a bzr:revision-id revprop set
563 revprops = lazy_dict(lambda: self._log._get_transport().revprop_list(revnum))
564 fileprops = lazy_dict(lambda: self.branchprop_list.get_changed_properties(path, revnum))
565 (bzr_revno, revid) = mapping.get_revision_id(path, revprops, fileprops)
566 except SubversionException, (_, num):
567 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
568 raise NoSuchRevision(path, revnum)
573 revid = mapping.generate_revision_id(self.uuid, revnum, path)
574 self.revmap.insert_revid(revid, path, revnum, revnum,
578 def lookup_revision_id(self, revid, scheme=None):
579 """Parse an existing Subversion-based revision id.
581 :param revid: The revision id.
582 :param scheme: Optional branching scheme to use when searching for
584 :raises: NoSuchRevision
585 :return: Tuple with branch path, revision number and mapping.
587 def get_scheme(name):
588 assert isinstance(name, str)
589 return BranchingScheme.find_scheme(name)
593 (uuid, branch_path, revnum, mapping) = parse_revision_id(revid)
594 assert isinstance(branch_path, str)
595 assert isinstance(mapping, BzrSvnMapping)
596 if uuid == self.uuid:
597 return (branch_path, revnum, mapping)
598 # If the UUID doesn't match, this may still be a valid revision
599 # id; a revision from another SVN repository may be pushed into
601 except InvalidRevisionId:
604 # Check the record out of the revmap, if it exists
606 (branch_path, min_revnum, max_revnum, \
607 scheme) = self.revmap.lookup_revid(revid)
608 assert isinstance(branch_path, str)
609 assert isinstance(scheme, str)
610 # Entry already complete?
611 if min_revnum == max_revnum:
612 return (branch_path, min_revnum, BzrSvnMappingv3FileProps(get_scheme(scheme)))
613 except NoSuchRevision, e:
614 # If there is no entry in the map, walk over all branches:
616 scheme = self.get_scheme()
617 last_revnum = self.transport.get_latest_revnum()
618 if (last_revnum <= self.revmap.last_revnum_checked(str(scheme))):
619 # All revision ids in this repository for the current
620 # scheme have already been discovered. No need to
624 for (branch, revno, _) in self.find_branchpaths(scheme,
625 self.revmap.last_revnum_checked(str(scheme)),
627 assert isinstance(branch, str)
628 assert isinstance(revno, int)
629 # Look at their bzr:revision-id-vX
632 props = self.branchprop_list.get_properties(branch, revno)
633 for line in props.get(SVN_PROP_BZR_REVISION_ID+str(scheme), "").splitlines():
635 revids.append(parse_revid_property(line))
636 except errors.InvalidPropertyValue, ie:
638 except SubversionException, (_, svn.core.SVN_ERR_FS_NOT_DIRECTORY):
641 # If there are any new entries that are not yet in the cache,
643 for (entry_revno, entry_revid) in revids:
644 if entry_revid == revid:
646 self.revmap.insert_revid(entry_revid, branch, 0, revno,
649 # We've added all the revision ids for this scheme in the repository,
650 # so no need to check again unless new revisions got added
651 self.revmap.set_last_revnum_checked(str(scheme), last_revnum)
654 (branch_path, min_revnum, max_revnum, scheme) = self.revmap.lookup_revid(revid)
655 assert isinstance(branch_path, str)
657 # Find the branch property between min_revnum and max_revnum that
659 for (bp, rev) in self.follow_branch(branch_path, max_revnum,
660 get_scheme(str(scheme))):
662 (entry_revno, entry_revid) = parse_revid_property(
663 self.branchprop_list.get_property_diff(bp, rev,
664 SVN_PROP_BZR_REVISION_ID+str(scheme)).strip("\n"))
665 except errors.InvalidPropertyValue:
666 # Don't warn about encountering an invalid property,
667 # that will already have happened earlier
669 if entry_revid == revid:
670 self.revmap.insert_revid(revid, bp, rev, rev, scheme)
671 return (bp, rev, BzrSvnMappingv3FileProps(get_scheme(scheme)))
673 raise AssertionError("Revision id %s was added incorrectly" % revid)
675 def get_inventory_xml(self, revision_id):
676 """See Repository.get_inventory_xml()."""
677 return self.serialise_inventory(self.get_inventory(revision_id))
679 def get_inventory_sha1(self, revision_id):
680 """Get the sha1 for the XML representation of an inventory.
682 :param revision_id: Revision id of the inventory for which to return
687 return osutils.sha_string(self.get_inventory_xml(revision_id))
689 def get_revision_xml(self, revision_id):
690 """Return the XML representation of a revision.
692 :param revision_id: Revision for which to return the XML.
695 return self._serializer.write_revision_to_string(
696 self.get_revision(revision_id))
698 def follow_history(self, revnum, mapping):
699 """Yield all the branches found between the start of history
700 and a specified revision number.
702 :param revnum: Revision number up to which to search.
703 :return: iterator over branches in the range 0..revnum
705 assert mapping is not None
707 for (_, paths, revnum) in self._log.iter_changes("", revnum):
711 bp = mapping.scheme.unprefix(p)[0]
712 if not bp in yielded_paths:
713 if not paths.has_key(bp) or paths[bp][0] != 'D':
714 assert revnum > 0 or bp == ""
716 yielded_paths.append(bp)
717 except NotBranchError:
720 def get_lhs_parent(self, revid):
721 (branch_path, revnum, mapping) = self.lookup_revision_id(revid)
722 parent_revid = self.lhs_revision_parent(branch_path, revnum, mapping)
723 if parent_revid is None:
727 def follow_branch(self, branch_path, revnum, mapping):
728 """Follow the history of a branch. Will yield all the
729 left-hand side ancestors of a specified revision.
731 :param branch_path: Subversion path to search.
732 :param revnum: Revision number in Subversion to start.
733 :param mapping: Mapping.
734 :return: iterator over the ancestors
736 assert branch_path is not None
737 assert isinstance(branch_path, str)
738 assert isinstance(revnum, int) and revnum >= 0
739 assert mapping.is_branch(branch_path) or mapping.is_tag(branch_path)
740 branch_path = branch_path.strip("/")
742 for (path, paths, revnum) in self._log.iter_changes(branch_path, revnum):
743 if not mapping.is_branch(path) and not mapping.is_tag(path):
744 # FIXME: if copyfrom_path is not a branch path,
745 # should simulate a reverse "split" of a branch
746 # for now, just make it look like the branch ended here
750 def follow_branch_history(self, branch_path, revnum, mapping):
751 """Return all the changes that happened in a branch
752 until branch_path,revnum.
754 :return: iterator that returns tuples with branch path,
755 changed paths and revision number.
757 assert isinstance(branch_path, str)
758 assert mapping.is_branch(branch_path) or mapping.is_tag(branch_path), \
759 "Mapping %r doesn't accept %s as branch or tag" % (mapping, branch_path)
761 for (bp, paths, revnum) in self._log.iter_changes(branch_path, revnum):
762 assert revnum > 0 or bp == ""
763 assert mapping.is_branch(bp) or mapping.is_tag(bp)
764 # Remove non-bp paths from paths
765 for p in paths.keys():
766 if not p.startswith(bp+"/") and bp != p and bp != "":
772 if (paths.has_key(bp) and paths[bp][1] is not None and
773 not mapping.is_branch(paths[bp][1]) and
774 not mapping.is_tag(paths[bp][1])):
775 # FIXME: if copyfrom_path is not a branch path,
776 # should simulate a reverse "split" of a branch
777 # for now, just make it look like the branch ended here
778 for c in self._log.find_children(paths[bp][1], paths[bp][2]):
779 path = c.replace(paths[bp][1], bp+"/", 1).replace("//", "/")
780 paths[path] = ('A', None, -1)
781 paths[bp] = ('A', None, -1)
783 yield (bp, paths, revnum)
786 yield (bp, paths, revnum)
788 def get_config(self):
789 return SvnRepositoryConfig(self.uuid)
791 def has_signature_for_revision_id(self, revision_id):
792 """Check whether a signature exists for a particular revision id.
794 :param revision_id: Revision id for which the signatures should be looked up.
795 :return: False, as no signatures are stored for revisions in Subversion
799 (path, revnum, mapping) = self.lookup_revision_id(revision_id)
800 except NoSuchRevision:
802 revprops = self.transport.revprop_list(revnum)
803 return revprops.has_key(SVN_REVPROP_BZR_SIGNATURE)
805 def get_signature_text(self, revision_id):
806 """Return the signature text for a particular revision.
808 :param revision_id: Id of the revision for which to return the
810 :raises NoSuchRevision: Always
812 (path, revnum, mapping) = self.lookup_revision_id(revision_id)
813 revprops = self.transport.revprop_list(revnum)
815 return revprops[SVN_REVPROP_BZR_SIGNATURE]
817 raise NoSuchRevision(self, revision_id)
819 def add_signature_text(self, revision_id, signature):
820 (path, revnum, mapping) = self.lookup_revision_id(revision_id)
821 self.transport.change_rev_prop(revnum, SVN_REVPROP_BZR_SIGNATURE, signature)
823 def get_revision_graph(self, revision_id=None):
824 """See Repository.get_revision_graph()."""
825 graph = self.get_graph()
827 if revision_id is None:
828 revision_ids = self.all_revision_ids(self.get_mapping())
830 revision_ids = [revision_id]
833 for (revid, parents) in graph.iter_ancestry(revision_ids):
834 if revid == NULL_REVISION:
836 if (NULL_REVISION,) == parents:
841 if revision_id is not None and revision_id != NULL_REVISION and ret[revision_id] is None:
842 raise NoSuchRevision(self, revision_id)
846 def find_branches(self, using=False, scheme=None):
847 """Find branches underneath this repository.
849 This will include branches inside other branches.
851 :param using: If True, list only branches using this repository.
853 # All branches use this repository, so the using argument can be
856 scheme = self.get_scheme()
858 existing_branches = [bp for (bp, revnum, _) in
859 filter(lambda (bp, rev, exists): exists,
860 self.find_branchpaths(scheme))]
863 for bp in existing_branches:
865 branches.append(Branch.open(urlutils.join(self.base, bp)))
866 except NotBranchError: # Skip non-directories
870 def find_branchpaths(self, scheme, from_revnum=0, to_revnum=None):
871 """Find all branch paths that were changed in the specified revision
874 :param revnum: Revision to search for branches.
875 :return: iterator that returns tuples with (path, revision number, still exists). The revision number is the revision in which the branch last existed.
877 assert scheme is not None
878 if to_revnum is None:
879 to_revnum = self.transport.get_latest_revnum()
881 created_branches = {}
885 pb = ui.ui_factory.nested_progress_bar()
887 for i in range(from_revnum, to_revnum+1):
888 pb.update("finding branches", i, to_revnum+1)
889 paths = self._log.get_revision_paths(i)
890 for p in sorted(paths.keys()):
891 if scheme.is_branch(p) or scheme.is_tag(p):
892 if paths[p][0] in ('R', 'D') and p in created_branches:
893 del created_branches[p]
895 prev_path = paths[p][1]
896 prev_rev = paths[p][2]
899 prev_rev = self._log.find_latest_change(p,
900 i-1, include_parents=True,
901 include_children=True)
902 assert isinstance(prev_rev, int)
903 ret.append((prev_path, prev_rev, False))
905 if paths[p][0] in ('A', 'R'):
906 created_branches[p] = i
907 elif scheme.is_branch_parent(p) or \
908 scheme.is_tag_parent(p):
909 if paths[p][0] in ('R', 'D'):
910 k = created_branches.keys()
912 if c.startswith(p+"/") and c in created_branches:
913 del created_branches[c]
914 j = self._log.find_latest_change(c, i-1,
915 include_parents=True,
916 include_children=True)
917 assert isinstance(j, int)
918 ret.append((c, j, False))
919 if paths[p][0] in ('A', 'R'):
924 for c in self.transport.get_dir(p, i)[0].keys():
926 if scheme.is_branch(n) or scheme.is_tag(n):
927 created_branches[n] = i
928 elif (scheme.is_branch_parent(n) or
929 scheme.is_tag_parent(n)):
931 except SubversionException, (_, svn.core.SVN_ERR_FS_NOT_DIRECTORY):
936 pb = ui.ui_factory.nested_progress_bar()
938 for p in created_branches:
939 pb.update("determining branch last changes",
940 i, len(created_branches))
941 j = self._log.find_latest_change(p, to_revnum,
942 include_parents=True,
943 include_children=True)
945 j = created_branches[p]
946 assert isinstance(j, int)
947 ret.append((p, j, True))
954 """Return True if this repository is flagged as a shared repository."""
957 def get_physical_lock_status(self):
960 def get_commit_builder(self, branch, parents, config, timestamp=None,
961 timezone=None, committer=None, revprops=None,
963 from commit import SvnCommitBuilder
964 return SvnCommitBuilder(self, branch, parents, config, timestamp,
965 timezone, committer, revprops, revision_id)