"""Subversion repository access."""
import bzrlib
-from bzrlib.branch import BranchCheckResult
-from bzrlib.errors import (InvalidRevisionId, NoSuchRevision,
- NotBranchError, UninitializableFormat, BzrError)
+from bzrlib import osutils, ui, urlutils
+from bzrlib.branch import Branch, BranchCheckResult
+from bzrlib.errors import (InvalidRevisionId, NoSuchRevision, NotBranchError,
+ UninitializableFormat, UnrelatedBranches)
from bzrlib.inventory import Inventory
from bzrlib.lockable_files import LockableFiles, TransportLock
-import bzrlib.osutils as osutils
from bzrlib.repository import Repository, RepositoryFormat
from bzrlib.revisiontree import RevisionTree
from bzrlib.revision import Revision, NULL_REVISION
-from bzrlib.transport import Transport
-from bzrlib.timestamp import unpack_highres_date, format_highres_date
-from bzrlib.trace import mutter
+from bzrlib.transport import Transport, get_transport
+from bzrlib.trace import info, mutter
from svn.core import SubversionException, Pool
import svn.core
from branchprops import BranchPropertyList
from cache import create_cache_dir, sqlite3
+import calendar
from config import SvnRepositoryConfig
import errors
import logwalker
-from revids import (generate_svn_revision_id, parse_svn_revision_id,
- MAPPING_VERSION, RevidMap)
-from scheme import BranchingScheme
+from mapping import (default_mapping, SVN_PROP_BZR_REVISION_ID,
+ SVN_PROP_BZR_REVISION_INFO, SVN_PROP_BZR_BRANCHING_SCHEME,
+ SVN_PROP_BZR_ANCESTRY, SVN_PROP_BZR_FILEIDS,
+ parse_revision_metadata, parse_revid_property)
+
+from revids import RevidMap
+from scheme import (BranchingScheme, ListBranchingScheme,
+ parse_list_scheme_text, guess_scheme_from_history)
from tree import SvnRevisionTree
+import urllib
-SVN_PROP_BZR_PREFIX = 'bzr:'
-SVN_PROP_BZR_MERGE = 'bzr:merge'
-SVN_PROP_BZR_FILEIDS = 'bzr:file-ids'
SVN_PROP_SVK_MERGE = 'svk:merge'
-SVN_PROP_BZR_FILEIDS = 'bzr:file-ids'
-SVN_PROP_BZR_REVISION_INFO = 'bzr:revision-info'
-SVN_REVPROP_BZR_SIGNATURE = 'bzr:gpg-signature'
-SVN_PROP_BZR_REVISION_ID = 'bzr:revision-id-v%d:' % MAPPING_VERSION
-
-def parse_revid_property(line):
- """Parse a (revnum, revid) tuple as set in revision id properties.
- :param line: line to parse
- :return: tuple with (bzr_revno, revid)
- """
- assert not '\n' in line
- try:
- (revno, revid) = line.split(' ', 1)
- except ValueError:
- raise errors.InvalidPropertyValue(SVN_PROP_BZR_REVISION_ID,
- "missing space")
- if revid == "":
- raise errors.InvalidPropertyValue(SVN_PROP_BZR_REVISION_ID,
- "empty revision id")
- return (int(revno), revid)
-
-def parse_revision_metadata(text, rev):
- """Parse a revision info text (as set in bzr:revision-info).
-
- :param text: text to parse
- :param rev: Revision object to apply read parameters to
- """
- in_properties = False
- for l in text.splitlines():
- try:
- key, value = l.split(": ", 2)
- except ValueError:
- raise errors.InvalidPropertyValue(SVN_PROP_BZR_REVISION_INFO,
- "Missing : in revision metadata")
- if key == "committer":
- rev.committer = str(value)
- elif key == "timestamp":
- (rev.timestamp, rev.timezone) = unpack_highres_date(value)
- elif key == "properties":
- in_properties = True
- elif key[0] == "\t" and in_properties:
- rev.properties[str(key[1:])] = str(value)
- else:
- raise errors.InvalidPropertyValue(SVN_PROP_BZR_REVISION_INFO,
- "Invalid key %r" % key)
-
-
-def generate_revision_metadata(timestamp, timezone, committer, revprops):
- """Generate revision metadata text for the specified revision
- properties.
-
- :param timestamp: timestamp of the revision, in seconds since epoch
- :param timezone: timezone, specified by offset from GMT in seconds
- :param committer: name/email of the committer
- :param revprops: dictionary with custom revision properties
- :return: text with data to set bzr:revision-info to.
- """
- assert timestamp is None or isinstance(timestamp, float)
- text = ""
- if timestamp is not None:
- text += "timestamp: %s\n" % format_highres_date(timestamp, timezone)
- if committer is not None:
- text += "committer: %s\n" % committer
- if revprops is not None and revprops != {}:
- text += "properties: \n"
- for k, v in sorted(revprops.items()):
- text += "\t%s: %s\n" % (k, v)
- return text
-
-
-def svk_feature_to_revision_id(feature, scheme):
- """Create a revision id from a svk feature identifier.
+def parse_svk_feature(feature):
+ """Parse a svk feature identifier.
:param feature: The feature identifier as string.
- :param scheme: Branching scheme name
- :return: Matching revision id.
+ :return: tuple with uuid, branch path and revnum
"""
- (uuid, branch, revnum) = feature.split(":")
- return generate_svn_revision_id(uuid, int(revnum), branch.strip("/"),
- scheme)
+ try:
+ (uuid, branch, revnum) = feature.split(":", 3)
+ except ValueError:
+ raise errors.InvalidPropertyValue(SVN_PROP_SVK_MERGE,
+ "not enough colons")
+ return (uuid, branch.strip("/"), int(revnum))
def revision_id_to_svk_feature(revid):
:param revid: Revision id to convert.
:return: Matching SVK feature identifier.
"""
- (uuid, branch, revnum, scheme) = parse_svn_revision_id(revid)
+ assert isinstance(revid, str)
+ (uuid, branch, revnum, _) = default_mapping.parse_revision_id(revid)
+ # TODO: What about renamed revisions? Should use
+ # repository.lookup_revision_id here.
return "%s:/%s:%d" % (uuid, branch, revnum)
"""
rich_root_data = True
+ def __get_matchingbzrdir(self):
+ from remote import SvnRemoteFormat
+ return SvnRemoteFormat()
+
+ _matchingbzrdir = property(__get_matchingbzrdir)
+
def __init__(self):
super(SvnRepositoryFormat, self).__init__()
def check_conversion_target(self, target_repo_format):
return target_repo_format.rich_root_data
+CACHE_DB_VERSION = 3
+
cachedbs = {}
class SvnRepository(Repository):
Provides a simplified interface to a Subversion repository
by using the RA (remote access) API from subversion
"""
- def __init__(self, bzrdir, transport):
+ def __init__(self, bzrdir, transport, branch_path=None):
+ from bzrlib.plugins.svn import lazy_register_optimizers
+ lazy_register_optimizers()
from fileids import SimpleFileIdMap
_revision_store = None
self.transport = transport
self.uuid = transport.get_uuid()
+ assert self.uuid is not None
self.base = transport.base
+ assert self.base is not None
self._serializer = None
self.dir_cache = {}
- self.scheme = bzrdir.scheme
self.pool = Pool()
- self.config = SvnRepositoryConfig(self.uuid)
- self.config.add_location(self.base)
-
- assert self.base
- assert self.uuid
-
- cache_file = os.path.join(self.create_cache_dir(),
- 'cache-v%d' % MAPPING_VERSION)
+ self.get_config().add_location(self.base)
+ cache_dir = self.create_cache_dir()
+ cachedir_transport = get_transport(cache_dir)
+ cache_file = os.path.join(cache_dir, 'cache-v%d' % CACHE_DB_VERSION)
if not cachedbs.has_key(cache_file):
cachedbs[cache_file] = sqlite3.connect(cache_file)
self.cachedb = cachedbs[cache_file]
- self._latest_revnum = transport.get_latest_revnum()
self._log = logwalker.LogWalker(transport=transport,
- cache_db=self.cachedb,
- last_revnum=self._latest_revnum)
+ cache_db=self.cachedb)
self.branchprop_list = BranchPropertyList(self._log, self.cachedb)
- self.fileid_map = SimpleFileIdMap(self, self.cachedb)
+ self.fileid_map = SimpleFileIdMap(self, cachedir_transport)
self.revmap = RevidMap(self.cachedb)
+ self._scheme = None
+ self._hinted_branch_path = branch_path
+
+ def lhs_missing_revisions(self, revhistory, stop_revision):
+ missing = []
+ slice = revhistory[:revhistory.index(stop_revision)+1]
+ for revid in reversed(slice):
+ if self.has_revision(revid):
+ missing.reverse()
+ return missing
+ missing.append(revid)
+ raise UnrelatedBranches()
+
+ def get_transaction(self):
+ raise NotImplementedError(self.get_transaction)
+
+ def get_stored_scheme(self):
+ """Retrieve the stored branching scheme, either in the repository
+ or in the configuration file.
+ """
+ scheme = self.get_config().get_branching_scheme()
+ if scheme is not None:
+ return (scheme, self.get_config().branching_scheme_is_mandatory())
- def set_branching_scheme(self, scheme):
- self.scheme = scheme
- self.config.set_branching_scheme(str(scheme))
+ last_revnum = self.transport.get_latest_revnum()
+ scheme = self._get_property_scheme(last_revnum)
+ if scheme is not None:
+ return (scheme, True)
+
+ return (None, False)
+
+ def get_scheme(self):
+ """Determine the branching scheme to use for this repository.
+
+ :return: Branching scheme.
+ """
+ # First, try to use the branching scheme we already know
+ if self._scheme is not None:
+ return self._scheme
+
+ (scheme, mandatory) = self.get_stored_scheme()
+ if mandatory:
+ self._scheme = scheme
+ return scheme
+
+ if scheme is not None:
+ if (self._hinted_branch_path is None or
+ scheme.is_branch(self._hinted_branch_path)):
+ self._scheme = scheme
+ return scheme
+
+ last_revnum = self.transport.get_latest_revnum()
+ self.set_branching_scheme(
+ self._guess_scheme(last_revnum, self._hinted_branch_path),
+ store=(last_revnum > 20),
+ mandatory=False)
+
+ return self._scheme
+
+ def _get_property_scheme(self, revnum=None):
+ if revnum is None:
+ revnum = self.transport.get_latest_revnum()
+ text = self.branchprop_list.get_property("",
+ revnum, SVN_PROP_BZR_BRANCHING_SCHEME, None)
+ if text is None:
+ return None
+ return ListBranchingScheme(parse_list_scheme_text(text))
+
+ def set_property_scheme(self, scheme):
+ def done(revision, date, author):
+ pass
+ editor = self.transport.get_commit_editor(
+ {svn.core.SVN_PROP_REVISION_LOG: "Updating branching scheme for Bazaar."},
+ done, None, False)
+ root = editor.open_root(-1)
+ editor.change_dir_prop(root, SVN_PROP_BZR_BRANCHING_SCHEME,
+ "".join(map(lambda x: x+"\n", scheme.branch_list)).encode("utf-8"))
+ editor.close_directory(root)
+ editor.close()
+
+ def _guess_scheme(self, last_revnum, branch_path=None):
+ scheme = guess_scheme_from_history(
+ self._log.follow_path("", last_revnum), last_revnum,
+ branch_path)
+ mutter("Guessed branching scheme: %r" % scheme)
+ return scheme
+
+ def set_branching_scheme(self, scheme, store=True, mandatory=False):
+ self._scheme = scheme
+ if store:
+ self.get_config().set_branching_scheme(str(scheme),
+ mandatory=mandatory)
def _warn_if_deprecated(self):
# This class isn't deprecated
cache_dir = create_cache_dir()
dir = os.path.join(cache_dir, self.uuid)
if not os.path.exists(dir):
+ info("Initialising Subversion metadata cache in %s" % dir)
os.mkdir(dir)
return dir
def all_revision_ids(self, scheme=None):
if scheme is None:
- scheme = self.scheme
+ scheme = self.get_scheme()
for (bp, rev) in self.follow_history(
self.transport.get_latest_revnum(), scheme):
yield self.generate_revision_id(rev, bp, str(scheme))
def get_inventory_weave(self):
+ """See Repository.get_inventory_weave()."""
raise NotImplementedError(self.get_inventory_weave)
def set_make_working_trees(self, new_value):
"""
return False
- def get_ancestry(self, revision_id):
+ def get_ancestry(self, revision_id, topo_sorted=True):
"""See Repository.get_ancestry().
Note: only the first bit is topologically ordered!
ancestry = [revision_id]
for l in self.branchprop_list.get_property(path, revnum,
- SVN_PROP_BZR_MERGE, "").splitlines():
+ SVN_PROP_BZR_ANCESTRY+str(scheme), "").splitlines():
ancestry.extend(l.split("\n"))
if revnum > 0:
for (branch, rev) in self.follow_branch(path, revnum - 1, scheme):
- ancestry.append(self.generate_revision_id(rev, branch, scheme))
+ ancestry.append(
+ self.generate_revision_id(rev, branch, str(scheme)))
ancestry.append(None)
ancestry.reverse()
return True
try:
- (path, revnum, scheme) = self.lookup_revision_id(revision_id)
+ (path, revnum, _) = self.lookup_revision_id(revision_id)
except NoSuchRevision:
return False
try:
- return (svn.core.svn_node_none != self.transport.check_path(path, revnum))
+ return (svn.core.svn_node_dir == self.transport.check_path(path, revnum))
except SubversionException, (_, num):
if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
return False
raise
+
def revision_trees(self, revids):
"""See Repository.revision_trees()."""
for revid in revids:
return SvnRevisionTree(self, revision_id)
def revision_fileid_renames(self, revid):
- """Check which files were renamed in a particular revision."""
- (path, revnum, scheme) = self.lookup_revision_id(revid)
- items = self.branchprop_list.get_property_diff(path, revnum,
- SVN_PROP_BZR_FILEIDS).splitlines()
- return dict(map(lambda x: x.split("\t"), items))
+ """Check which files were renamed in a particular revision.
+
+ :param revid: Id of revision to look up.
+ :return: dictionary with paths as keys, file ids as values
+ """
+ (path, revnum, _) = self.lookup_revision_id(revid)
+ # Only consider bzr:file-ids if this is a bzr revision
+ if not self.branchprop_list.touches_property(path, revnum,
+ SVN_PROP_BZR_REVISION_INFO):
+ return {}
+ fileids = self.branchprop_list.get_property(path, revnum,
+ SVN_PROP_BZR_FILEIDS)
+ if fileids is None:
+ return {}
+ ret = {}
+ for line in fileids.splitlines():
+ (path, key) = line.split("\t", 2)
+ ret[urllib.unquote(path)] = osutils.safe_file_id(key)
+ return ret
def _mainline_revision_parent(self, path, revnum, scheme):
"""Find the mainline parent of the specified revision.
:return: Revision id of the left-hand-side parent or None if
this is the first revision
"""
- assert isinstance(path, basestring)
+ assert isinstance(path, str)
assert isinstance(revnum, int)
if not scheme.is_branch(path) and \
not scheme.is_tag(path):
raise NoSuchRevision(self,
- self.generate_revision_id(revnum, path, scheme))
+ self.generate_revision_id(revnum, path, str(scheme)))
it = self.follow_branch(path, revnum, scheme)
# the first tuple returned should match the one specified.
# revision and so it is invalid
if (path, revnum) != it.next():
raise NoSuchRevision(self,
- self.generate_revision_id(revnum, path, scheme))
+ self.generate_revision_id(revnum, path, str(scheme)))
try:
(branch, rev) = it.next()
- return self.generate_revision_id(rev, branch, scheme)
+ return self.generate_revision_id(rev, branch, str(scheme))
except StopIteration:
# The specified revision was the first one in the branch
return None
- def revision_parents(self, revision_id, merged_data=None):
+ def _bzr_merged_revisions(self, branch, revnum, scheme):
+ """Find out what revisions were merged by Bazaar in a revision.
+
+ :param branch: Subversion branch path.
+ :param revnum: Subversion revision number.
+ :param scheme: Branching scheme.
+ """
+ change = self.branchprop_list.get_property_diff(branch, revnum,
+ SVN_PROP_BZR_ANCESTRY+str(scheme)).splitlines()
+ if len(change) == 0:
+ return []
+
+ assert len(change) == 1
+
+ return parse_merge_property(change[0])
+
+ def _svk_feature_to_revision_id(self, scheme, feature):
+ """Convert a SVK feature to a revision id for this repository.
+
+ :param scheme: Branching scheme.
+ :param feature: SVK feature.
+ :return: revision id.
+ """
+ try:
+ (uuid, bp, revnum) = parse_svk_feature(feature)
+ except errors.InvalidPropertyValue:
+ return None
+ if uuid != self.uuid:
+ return None
+ if not scheme.is_branch(bp) and not scheme.is_tag(bp):
+ return None
+ return self.generate_revision_id(revnum, bp, str(scheme))
+
+ def _svk_merged_revisions(self, branch, revnum, scheme):
+ """Find out what SVK features were merged in a revision.
+
+ :param branch: Subversion branch path.
+ :param revnum: Subversion revision number.
+ :param scheme: Branching scheme.
+ """
+ current = set(self.branchprop_list.get_property(branch, revnum, SVN_PROP_SVK_MERGE, "").splitlines())
+ (prev_path, prev_revnum) = self._log.get_previous(branch, revnum)
+ if prev_path is None and prev_revnum == -1:
+ previous = set()
+ else:
+ previous = set(self.branchprop_list.get_property(prev_path.encode("utf-8"),
+ prev_revnum, SVN_PROP_SVK_MERGE, "").splitlines())
+ for feature in current.difference(previous):
+ revid = self._svk_feature_to_revision_id(scheme, feature)
+ if revid is not None:
+ yield revid
+
+ def get_parents(self, revids):
+ parents_list = []
+ for revision_id in revids:
+ if revision_id == NULL_REVISION:
+ parents = []
+ else:
+ try:
+ parents = self.revision_parents(revision_id)
+ except NoSuchRevision:
+ parents = None
+ else:
+ if len(parents) == 0:
+ parents = [NULL_REVISION]
+ parents_list.append(parents)
+ return parents_list
+
+ def revision_parents(self, revision_id, bzr_merges=None, svk_merges=None):
+ """See Repository.revision_parents()."""
parent_ids = []
(branch, revnum, scheme) = self.lookup_revision_id(revision_id)
mainline_parent = self._mainline_revision_parent(branch, revnum, scheme)
if mainline_parent is not None:
parent_ids.append(mainline_parent)
- (parent_path, parent_revnum, scheme) = self.lookup_revision_id(mainline_parent)
- else:
- parent_path = None
- # if the branch didn't change, bzr:merge can't have changed
+ # if the branch didn't change, bzr:merge or svk:merge can't have changed
if not self._log.touches_path(branch, revnum):
return parent_ids
- if merged_data is None:
- new_merge = self.branchprop_list.get_property(branch, revnum,
- SVN_PROP_BZR_MERGE, "").splitlines()
-
- if len(new_merge) == 0 or parent_path is None:
- old_merge = ""
- else:
- old_merge = self.branchprop_list.get_property(parent_path, parent_revnum,
- SVN_PROP_BZR_MERGE, "").splitlines()
-
- assert (len(old_merge) == len(new_merge) or
- len(old_merge) + 1 == len(new_merge))
-
- if len(old_merge) < len(new_merge):
- merged_data = new_merge[-1]
- else:
- merged_data = ""
+ if bzr_merges is None:
+ bzr_merges = self._bzr_merged_revisions(branch, revnum, scheme)
+ if svk_merges is None:
+ svk_merges = self._svk_merged_revisions(branch, revnum, scheme)
- if ' ' in merged_data:
- mutter('invalid revision id %r in merged property, skipping' % merged_data)
- merged_data = ""
+ parent_ids.extend(bzr_merges)
- if merged_data != "":
- parent_ids.extend(merged_data.split("\t"))
+ if bzr_merges == []:
+ # Commit was doing using svk apparently
+ parent_ids.extend(svk_merges)
return parent_ids
def get_revision(self, revision_id):
"""See Repository.get_revision."""
- if not revision_id or not isinstance(revision_id, basestring):
+ if not revision_id or not isinstance(revision_id, str):
raise InvalidRevisionId(revision_id=revision_id, branch=self)
- (path, revnum, scheme) = self.lookup_revision_id(revision_id)
+ (path, revnum, _) = self.lookup_revision_id(revision_id)
parent_ids = self.revision_parents(revision_id)
# Commit SVN revision properties to a Revision object
- rev = Revision(revision_id=revision_id, parent_ids=parent_ids)
+ class LazySvnRevision(Revision):
+ inventory_sha1 = property(lambda rev: self.get_inventory_sha1(rev.revision_id))
+
+ rev = LazySvnRevision(revision_id=revision_id, parent_ids=parent_ids)
- (rev.committer, rev.message, date) = self._log.get_revision_info(revnum)
- if rev.committer is None:
+ svn_revprops = self.transport.revprop_list(revnum)
+
+ if svn_revprops.has_key(svn.core.SVN_PROP_REVISION_AUTHOR):
+ rev.committer = svn_revprops[svn.core.SVN_PROP_REVISION_AUTHOR]
+ else:
rev.committer = ""
- if date is not None:
- rev.timestamp = 1.0 * svn.core.secs_from_timestr(date, None)
+ rev.message = svn_revprops.get(svn.core.SVN_PROP_REVISION_LOG)
+
+ if rev.message:
+ try:
+ rev.message = rev.message.decode("utf-8")
+ except UnicodeDecodeError:
+ pass
+
+ if svn_revprops.has_key(svn.core.SVN_PROP_REVISION_DATE):
+ rev.timestamp = 1.0 * svn.core.secs_from_timestr(svn_revprops[svn.core.SVN_PROP_REVISION_DATE], None)
else:
rev.timestamp = 0.0 # FIXME: Obtain repository creation time
rev.timezone = None
self.branchprop_list.get_property(path, revnum,
SVN_PROP_BZR_REVISION_INFO, ""), rev)
- rev.inventory_sha1 = property(lambda: self.get_inventory_sha1(revision_id))
-
return rev
def get_revisions(self, revision_ids):
+ """See Repository.get_revisions()."""
# TODO: More efficient implementation?
return map(self.get_revision, revision_ids)
SVN_PROP_BZR_REVISION_ID+str(scheme)).strip("\n")
# Or generate it
if line == "":
- revid = generate_svn_revision_id(self.uuid, revnum, path,
- scheme)
+ revid = default_mapping.generate_revision_id(
+ self.uuid, revnum, path, scheme)
else:
try:
(bzr_revno, revid) = parse_revid_property(line)
scheme, bzr_revno)
except errors.InvalidPropertyValue, e:
mutter(str(e))
- revid = generate_svn_revision_id(self.uuid, revnum, path,
- scheme)
+ revid = default_mapping.generate_revision_id(self.uuid,
+ revnum, path, scheme)
self.revmap.insert_revid(revid, path, revnum, revnum,
scheme)
:raises: NoSuchRevision
:return: Tuple with branch path, revision number and scheme.
"""
+ def get_scheme(name):
+ assert isinstance(name, str)
+ return BranchingScheme.find_scheme(name)
# Try a simple parse
try:
- (uuid, branch_path, revnum, schemen) = parse_svn_revision_id(revid)
+ (uuid, branch_path, revnum, schemen) = default_mapping.parse_revision_id(revid)
assert isinstance(branch_path, str)
+ assert isinstance(schemen, str)
if uuid == self.uuid:
- return (branch_path, revnum, self.get_scheme(schemen))
+ return (branch_path, revnum, get_scheme(schemen))
# If the UUID doesn't match, this may still be a valid revision
# id; a revision from another SVN repository may be pushed into
# this one.
(branch_path, min_revnum, max_revnum, \
scheme) = self.revmap.lookup_revid(revid)
assert isinstance(branch_path, str)
+ assert isinstance(scheme, str)
# Entry already complete?
if min_revnum == max_revnum:
- return (branch_path, min_revnum, self.get_scheme(scheme))
- except NoSuchRevision:
+ return (branch_path, min_revnum, get_scheme(scheme))
+ except NoSuchRevision, e:
# If there is no entry in the map, walk over all branches:
if scheme is None:
- scheme = self.scheme # FIXME
- for (branch, revno, exists) in self.find_branches(scheme):
+ scheme = self.get_scheme()
+ last_revnum = self.transport.get_latest_revnum()
+ if (last_revnum <= self.revmap.last_revnum_checked(str(scheme))):
+ # All revision ids in this repository for the current
+ # scheme have already been discovered. No need to
+ # check again.
+ raise e
+ found = False
+ for (branch, revno, _) in self.find_branchpaths(scheme,
+ self.revmap.last_revnum_checked(str(scheme)),
+ last_revnum):
+ assert isinstance(branch, str)
+ assert isinstance(revno, int)
# Look at their bzr:revision-id-vX
revids = []
- for line in self.branchprop_list.get_property(branch, revno,
- SVN_PROP_BZR_REVISION_ID+str(scheme), "").splitlines():
- try:
- revids.append(parse_revid_property(line))
- except errors.InvalidPropertyValue, e:
- mutter(str(e))
+ try:
+ for line in self.branchprop_list.get_property(branch, revno,
+ SVN_PROP_BZR_REVISION_ID+str(scheme), "").splitlines():
+ try:
+ revids.append(parse_revid_property(line))
+ except errors.InvalidPropertyValue, ie:
+ mutter(str(ie))
+ except SubversionException, (_, svn.core.SVN_ERR_FS_NOT_DIRECTORY):
+ continue
# If there are any new entries that are not yet in the cache,
# add them
for (entry_revno, entry_revid) in revids:
+ if entry_revid == revid:
+ found = True
self.revmap.insert_revid(entry_revid, branch, 0, revno,
str(scheme), entry_revno)
-
- if revid in revids:
- break
+ # We've added all the revision ids for this scheme in the repository,
+ # so no need to check again unless new revisions got added
+ self.revmap.set_last_revnum_checked(str(scheme), last_revnum)
+ if not found:
+ raise e
(branch_path, min_revnum, max_revnum, scheme) = self.revmap.lookup_revid(revid)
assert isinstance(branch_path, str)
# Find the branch property between min_revnum and max_revnum that
# added revid
- i = min_revnum
for (bp, rev) in self.follow_branch(branch_path, max_revnum,
- self.get_scheme(scheme)):
+ get_scheme(str(scheme))):
try:
(entry_revno, entry_revid) = parse_revid_property(
self.branchprop_list.get_property_diff(bp, rev,
if entry_revid == revid:
self.revmap.insert_revid(revid, bp, rev, rev, scheme,
entry_revno)
- return (bp, rev, self.get_scheme(scheme))
+ return (bp, rev, get_scheme(scheme))
raise AssertionError("Revision id %s was added incorrectly" % revid)
def get_inventory_xml(self, revision_id):
+ """See Repository.get_inventory_xml()."""
return bzrlib.xml5.serializer_v5.write_inventory_to_string(
self.get_inventory(revision_id))
assert branch_path is not None
assert isinstance(branch_path, str)
assert isinstance(revnum, int) and revnum >= 0
- if not scheme.is_branch(branch_path) and \
- not scheme.is_tag(branch_path):
- raise errors.NotSvnBranchPath(branch_path, revnum)
+ assert scheme.is_branch(branch_path) or scheme.is_tag(branch_path)
branch_path = branch_path.strip("/")
while revnum >= 0:
+ assert revnum > 0 or branch_path == ""
paths = self._log.get_revision_paths(revnum)
yielded = False
# revision there, so yield it.
for p in paths:
assert isinstance(p, str)
- if p == branch_path or p.startswith(branch_path+"/") or branch_path == "":
+ if (p == branch_path or
+ p.startswith(branch_path+"/") or
+ branch_path == ""):
yield (branch_path, revnum)
yielded = True
break
# Path names need to be sorted so the longer paths
# override the shorter ones
- path_names = paths.keys()
- path_names.sort()
- for p in path_names:
+ for p in sorted(paths.keys(), reverse=True):
+ if paths[p][0] == 'M':
+ continue
if branch_path.startswith(p+"/"):
- assert paths[p][1] is not None and paths[p][0] in ('A', 'R'), "Parent didn't exist yet, but child wasn't added !?"
+ assert paths[p][0] in ('A', 'R'), "Parent wasn't added"
+ assert paths[p][1] is not None, \
+ "Empty parent added, but child wasn't added !?"
revnum = paths[p][2]
branch_path = paths[p][1].encode("utf-8") + branch_path[len(p):]
+ break
- """Return all the changes that happened in a branch
- between branch_path and revnum.
-
- :return: iterator that returns tuples with branch path,
- changed paths and revision number.
- """
def follow_branch_history(self, branch_path, revnum, scheme):
+ """Return all the changes that happened in a branch
+ between branch_path and revnum.
+
+ :return: iterator that returns tuples with branch path,
+ changed paths and revision number.
+ """
assert branch_path is not None
- if not scheme.is_branch(branch_path) and \
- not scheme.is_tag(branch_path):
- raise errors.NotSvnBranchPath(branch_path, revnum)
+ assert scheme.is_branch(branch_path) or scheme.is_tag(branch_path)
for (bp, paths, revnum) in self._log.follow_path(branch_path, revnum):
- if (paths.has_key(bp) and
- paths[bp][1] is not None and
+ assert revnum > 0 or bp == ""
+ assert scheme.is_branch(bp) or scheme.is_tag(bp)
+ # Remove non-bp paths from paths
+ for p in paths.keys():
+ if not p.startswith(bp+"/") and bp != p and bp != "":
+ del paths[p]
+
+ if paths == {}:
+ continue
+
+ if (paths.has_key(bp) and paths[bp][1] is not None and
not scheme.is_branch(paths[bp][1]) and
not scheme.is_tag(paths[bp][1])):
# FIXME: if copyfrom_path is not a branch path,
yield (bp, paths, revnum)
- """Check whether a signature exists for a particular revision id.
+ def get_config(self):
+ return SvnRepositoryConfig(self.uuid)
- :param revision_id: Revision id for which the signatures should be looked up.
- :return: False, as no signatures are stored for revisions in Subversion
- at the moment.
- """
def has_signature_for_revision_id(self, revision_id):
+ """Check whether a signature exists for a particular revision id.
+
+ :param revision_id: Revision id for which the signatures should be looked up.
+ :return: False, as no signatures are stored for revisions in Subversion
+ at the moment.
+ """
# TODO: Retrieve from SVN_PROP_BZR_SIGNATURE
return False # SVN doesn't store GPG signatures. Perhaps
# store in SVN revision property?
# SVN doesn't store GPG signatures
raise NoSuchRevision(self, revision_id)
- def _full_revision_graph(self, scheme):
+ def _full_revision_graph(self, scheme, _latest_revnum=None):
+ if _latest_revnum is None:
+ _latest_revnum = self.transport.get_latest_revnum()
graph = {}
- for (branch, revnum) in self.follow_history(self._latest_revnum,
+ for (branch, revnum) in self.follow_history(_latest_revnum,
scheme):
mutter('%r, %r' % (branch, revnum))
- revid = self.generate_revision_id(revnum, branch, scheme)
+ revid = self.generate_revision_id(revnum, branch, str(scheme))
graph[revid] = self.revision_parents(revid)
return graph
def get_revision_graph(self, revision_id=None):
+ """See Repository.get_revision_graph()."""
if revision_id == NULL_REVISION:
return {}
if revision_id is None:
- return self._full_revision_graph(self.scheme) # FIXME
+ return self._full_revision_graph(self.get_scheme())
(path, revnum, scheme) = self.lookup_revision_id(revision_id)
if revnum > 0:
for (branch, rev) in self.follow_branch(path, revnum - 1, scheme):
- revid = self.generate_revision_id(rev, branch, scheme)
+ revid = self.generate_revision_id(rev, branch, str(scheme))
self._ancestry[_previous] = [revid]
_previous = revid
return self._ancestry
- def find_branches(self, scheme, revnum=None, pb=None):
- """Find all branches that were changed in the specified revision number.
+ def find_branches(self, using=False):
+ """Find branches underneath this repository.
+
+ This will include branches inside other branches.
+
+ :param using: If True, list only branches using this repository.
+ """
+ # All branches use this repository, so the using argument can be
+ # ignored.
+ scheme = self.get_scheme()
+
+ existing_branches = [bp for (bp, revnum, _) in
+ filter(lambda (bp, rev, exists): exists,
+ self.find_branchpaths(scheme))]
+
+ branches = []
+ for bp in existing_branches:
+ try:
+ branches.append(Branch.open(urlutils.join(self.base, bp)))
+ except NotBranchError: # Skip non-directories
+ pass
+ return branches
+
+ def find_branchpaths(self, scheme, from_revnum=0, to_revnum=None):
+ """Find all branch paths that were changed in the specified revision
+ range.
:param revnum: Revision to search for branches.
:return: iterator that returns tuples with (path, revision number, still exists). The revision number is the revision in which the branch last existed.
"""
- if revnum is None:
- revnum = self.transport.get_latest_revnum()
+ assert scheme is not None
+ if to_revnum is None:
+ to_revnum = self.transport.get_latest_revnum()
created_branches = {}
- for i in range(revnum+1):
- if pb is not None:
- pb.update("finding branches", i, revnum+1)
- paths = self._log.get_revision_paths(i)
- names = paths.keys()
- names.sort()
- for p in names:
- if scheme.is_branch(p) or scheme.is_tag(p):
- if paths[p][0] in ('R', 'D'):
- del created_branches[p]
- j = self._log.find_latest_change(p, i-1, recurse=True)
- yield (p, j, False)
-
- if paths[p][0] in ('A', 'R'):
- created_branches[p] = i
- elif scheme.is_branch_parent(p) or \
- scheme.is_tag_parent(p):
- if paths[p][0] in ('R', 'D'):
- k = created_branches.keys()
- for c in k:
- if c.startswith(p+"/"):
- del created_branches[c]
- j = self._log.find_latest_change(c, i-1,
- recurse=True)
- yield (c, j, False)
- if paths[p][0] in ('A', 'R'):
- parents = [p]
- while parents:
- p = parents.pop()
- for c in self.transport.get_dir(p, i)[0].keys():
- n = p+"/"+c
- if scheme.is_branch(n) or scheme.is_tag(n):
- created_branches[n] = i
- elif scheme.is_branch_parent(n) or scheme.is_tag_parent(n):
- parents.append(n)
+ ret = []
+
+ pb = ui.ui_factory.nested_progress_bar()
+ try:
+ for i in range(from_revnum, to_revnum+1):
+ pb.update("finding branches", i, to_revnum+1)
+ paths = self._log.get_revision_paths(i)
+ for p in sorted(paths.keys()):
+ if scheme.is_branch(p) or scheme.is_tag(p):
+ if paths[p][0] in ('R', 'D') and p in created_branches:
+ del created_branches[p]
+ if paths[p][1]:
+ prev_path = paths[p][1]
+ prev_rev = paths[p][2]
+ else:
+ prev_path = p
+ prev_rev = self._log.find_latest_change(p,
+ i-1, include_parents=True,
+ include_children=True)
+ assert isinstance(prev_rev, int)
+ ret.append((prev_path, prev_rev, False))
+
+ if paths[p][0] in ('A', 'R'):
+ created_branches[p] = i
+ elif scheme.is_branch_parent(p) or \
+ scheme.is_tag_parent(p):
+ if paths[p][0] in ('R', 'D'):
+ k = created_branches.keys()
+ for c in k:
+ if c.startswith(p+"/") and c in created_branches:
+ del created_branches[c]
+ j = self._log.find_latest_change(c, i-1,
+ include_parents=True,
+ include_children=True)
+ assert isinstance(j, int)
+ ret.append((c, j, False))
+ if paths[p][0] in ('A', 'R'):
+ parents = [p]
+ while parents:
+ p = parents.pop()
+ try:
+ for c in self.transport.get_dir(p, i)[0].keys():
+ n = p+"/"+c
+ if scheme.is_branch(n) or scheme.is_tag(n):
+ created_branches[n] = i
+ elif (scheme.is_branch_parent(n) or
+ scheme.is_tag_parent(n)):
+ parents.append(n)
+ except SubversionException, (_, svn.core.SVN_ERR_FS_NOT_DIRECTORY):
+ pass
+ finally:
+ pb.finished()
for p in created_branches:
- j = self._log.find_latest_change(p, revnum, recurse=True)
+ j = self._log.find_latest_change(p, to_revnum,
+ include_parents=True,
+ include_children=True)
if j is None:
j = created_branches[p]
- yield (p, j, True)
+ assert isinstance(j, int)
+ ret.append((p, j, True))
+
+ return ret
def is_shared(self):
"""Return True if this repository is flagged as a shared repository."""
timezone, committer, revprops, revision_id)
- def get_scheme(self, name):
- assert isinstance(name, basestring)
- # FIXME: Support other branching scheme
- return BranchingScheme.find_scheme(name)