merge 0.4.8.
authorJelmer Vernooij <jelmer@samba.org>
Fri, 21 Mar 2008 22:41:48 +0000 (23:41 +0100)
committerJelmer Vernooij <jelmer@samba.org>
Fri, 21 Mar 2008 22:41:48 +0000 (23:41 +0100)
1  2 
Makefile
NEWS
fetch.py
mapping.py
repository.py
tests/test_commit.py
tests/test_repos.py
tests/test_revids.py

diff --cc Makefile
index 6ff0fe3f3e13a88da1f57f3bbcf3bb075cc7736c,871aa2972b22e3589645a5201af47fcf094b7f2e..d77d85c6666ba334fd7acb7a3a00e24b153da2e0
+++ b/Makefile
@@@ -26,11 -21,11 +26,11 @@@ TMP_PLUGINS_DIR = $(shell pwd)/.plugin
  $(TMP_PLUGINS_DIR):
        mkdir -p $@
  
- $(TMP_PLUGINS_DIR)/svn: build-inplace $(TMP_PLUGINS_DIR)
+ $(TMP_PLUGINS_DIR)/svn: $(TMP_PLUGINS_DIR)
 -      ln -sf $@ `pwd`
 +      ln -sf $@ ..
  
--check:: $(TMP_PLUGINS_DIR)/svn
 -      BZR_PLUGIN_PATH=$(TMP_PLUGINS_DIR) $(BZR) selftest $(TEST_OPTIONS) $(TESTS)
++check:: build-inplace $(TMP_PLUGINS_DIR)/svn 
 +      BZR_PLUGIN_PATH=$(TMP_PLUGINS_DIR) $(DEBUGGER) $(PYTHON) $(BZR) selftest $(TEST_OPTIONS) $(TESTS)
  
  check-verbose::
        $(MAKE) check TEST_OPTIONS=-v
diff --cc NEWS
index 430a352da48f6e97ccb335666ea1618492f1dfa0,c46d1d5df1124af04726cff5c4b099db2664755b..9ac7602d7d7e913ae792ca9e07351da0e24d526f
--- 1/NEWS
--- 2/NEWS
+++ b/NEWS
@@@ -1,28 -1,4 +1,28 @@@
- bzr-svn 0.4.8 2008-03-11
 +bzr-svn 0.4.9 UNRELEASED
 +
 +  FEATURES
 +
 +   * Set revision properties when possible.
 +
 +   * Avoid doing two branch property lookups when getting revision metadata.
 +
 +   * Use caching revision graph.
 +
 +   * --prefix is now determined from the specified url in svn-import. (#160335)
 +
 +  BUGS
 +  
 +   * Fix compatibility with bzr 1.3.
 +
 +   * Be a bit quieter with messages about experimental mappings. (#162496)
 +
 +   * More correct implementation of Repository.get_ancestry(). 
 +
 +  INTERNALS
 +
 +   * Allow multiple mappings to be available at the same time.
 +
+ bzr-svn 0.4.8 2008-03-21
  
    BUG FIXES
  
diff --cc fetch.py
index 074e6f6392e12add4f58eea3801b8d527e4f2eb2,b3fbca58ca01557d82154677615c3f1f98dff960..b83ce9c6a65d045db7ac1598a3de4379b3bd72f3
+++ b/fetch.py
@@@ -99,8 -99,11 +99,11 @@@ class RevisionBuildEditor(svn.delta.Edi
          renames = self.source.revision_fileid_renames(revid)
          self.id_map = self.source.transform_fileid_map(self.source.uuid, 
                                self.revnum, self.branch_path, changes, renames, 
 -                              self.scheme)
 +                              self.mapping)
          self.dir_baserev = {}
+         self._revinfo = None
+         self._bzr_merges = ()
+         self._svk_merges = []
          self._premature_deletes = set()
          self.pool = Pool()
          self.old_inventory = prev_inventory
diff --cc mapping.py
index 2035ff86b04fd949c2c05358b73641cbb43c4a42,847b5b7a3d98f51a3bffa8cf1b79c02f6549692d..9b24e1bf4d6b724aaa19bb85de6e720411e2e2ff
@@@ -61,219 -49,11 +61,219 @@@ def escape_svn_path(x)
  unescape_svn_path = urllib.unquote
  
  
-         return []
 +# The following two functions don't use day names (which can vary by 
 +# locale) unlike the alternatives in bzrlib.timestamp
 +
 +def format_highres_date(t, offset=0):
 +    """Format a date, such that it includes higher precision in the
 +    seconds field.
 +
 +    :param t:   The local time in fractional seconds since the epoch
 +    :type t: float
 +    :param offset:  The timezone offset in integer seconds
 +    :type offset: int
 +    """
 +    assert isinstance(t, float)
 +
 +    # This has to be formatted for "original" date, so that the
 +    # revision XML entry will be reproduced faithfully.
 +    if offset is None:
 +        offset = 0
 +    tt = time.gmtime(t + offset)
 +
 +    return (time.strftime("%Y-%m-%d %H:%M:%S", tt)
 +            # Get the high-res seconds, but ignore the 0
 +            + ('%.9f' % (t - int(t)))[1:]
 +            + ' %+03d%02d' % (offset / 3600, (offset / 60) % 60))
 +
 +
 +def unpack_highres_date(date):
 +    """This takes the high-resolution date stamp, and
 +    converts it back into the tuple (timestamp, timezone)
 +    Where timestamp is in real UTC since epoch seconds, and timezone is an
 +    integer number of seconds offset.
 +
 +    :param date: A date formated by format_highres_date
 +    :type date: string
 +    """
 +    # skip day if applicable
 +    if not date[0].isdigit():
 +        space_loc = date.find(' ')
 +        if space_loc == -1:
 +            raise ValueError("No valid date: %r" % date)
 +        date = date[space_loc+1:]
 +    # Up until the first period is a datestamp that is generated
 +    # as normal from time.strftime, so use time.strptime to
 +    # parse it
 +    dot_loc = date.find('.')
 +    if dot_loc == -1:
 +        raise ValueError(
 +            'Date string does not contain high-precision seconds: %r' % date)
 +    base_time = time.strptime(date[:dot_loc], "%Y-%m-%d %H:%M:%S")
 +    fract_seconds, offset = date[dot_loc:].split()
 +    fract_seconds = float(fract_seconds)
 +
 +    offset = int(offset)
 +
 +    hours = int(offset / 100)
 +    minutes = (offset % 100)
 +    seconds_offset = (hours * 3600) + (minutes * 60)
 +
 +    # time.mktime returns localtime, but calendar.timegm returns UTC time
 +    timestamp = calendar.timegm(base_time)
 +    timestamp -= seconds_offset
 +    # Add back in the fractional seconds
 +    timestamp += fract_seconds
 +    return (timestamp, seconds_offset)
 +
 +
 +def parse_merge_property(line):
 +    """Parse a bzr:merge property value.
 +
 +    :param line: Line to parse
 +    :return: List of revisions merged
 +    """
 +    if ' ' in line:
 +        mutter('invalid revision id %r in merged property, skipping' % line)
-     return filter(lambda x: x != "", line.split("\t"))
++        return ()
 +
++    return tuple(filter(lambda x: x != "", line.split("\t")))
 +
 +def parse_svn_revprops(svn_revprops, rev):
 +    if svn_revprops.has_key(svn.core.SVN_PROP_REVISION_AUTHOR):
 +        rev.committer = svn_revprops[svn.core.SVN_PROP_REVISION_AUTHOR]
 +    else:
 +        rev.committer = ""
 +
 +    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
 +    rev.properties = {}
 +
 +
 +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 = value.decode("utf-8")
 +        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:])] = value.decode("utf-8")
 +        else:
 +            raise errors.InvalidPropertyValue(SVN_PROP_BZR_REVISION_INFO, 
 +                    "Invalid key %r" % key)
 +
 +
 +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)
 +    """
 +    if '\n' in line:
 +        raise errors.InvalidPropertyValue(SVN_PROP_BZR_REVISION_ID, 
 +                "newline in revision id property 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 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 parse_bzr_svn_revprops(props, rev):
 +    """Update a Revision object from a set of Subversion revision properties.
 +    
 +    :param props: Dictionary with Subversion revision properties.
 +    :param rev: Revision object
 +    """
 +    if props.has_key(SVN_REVPROP_BZR_TIMESTAMP):
 +        (rev.timestamp, rev.timezone) = unpack_highres_date(props[SVN_REVPROP_BZR_TIMESTAMP])
 +
 +    if props.has_key(SVN_REVPROP_BZR_COMMITTER):
 +        rev.committer = props[SVN_REVPROP_BZR_COMMITTER].decode("utf-8")
 +
 +    for name, value in props.items():
 +        if name.startswith(SVN_REVPROP_BZR_REVPROP_PREFIX):
 +            rev.properties[name[len(SVN_REVPROP_BZR_REVPROP_PREFIX):]] = value
 +
 +
  class BzrSvnMapping:
      """Class that maps between Subversion and Bazaar semantics."""
 +    experimental = False
 +    _warned_experimental = False
  
 -    @staticmethod
 -    def parse_revision_id(revid):
 +    def __init__(self):
 +        if self.experimental and not BzrSvnMapping._warned_experimental:
 +            from bzrlib.trace import warning
 +            warning("using experimental bzr-svn mappings; output may change between revisions")
 +            BzrSvnMapping._warned_experimental = True
 +
 +    @classmethod
 +    def supports_roundtripping(cls):
 +        """Whether this mapping supports roundtripping.
 +        """
 +        return False
 +
 +    @classmethod
 +    def supports_custom_revprops(cls):
 +        """Whether this mapping can be used with custom revision properties."""
 +        return False
 +
 +    @classmethod
 +    def supports_custom_fileprops(cls):
 +        """Whether this mapping can be used with custom file properties."""
 +        return False
 +
 +    def parse_revision_id(self, revid):
          """Parse an existing Subversion-based revision id.
  
          :param revid: The revision id.
@@@ -507,253 -147,23 +507,251 @@@ class BzrSvnMappingv3(BzrSvnMapping)
          return "%s%s:%s:%s:%d" % (cls.revid_prefix, scheme, uuid, \
                         escape_svn_path(path.strip("/")), revnum)
  
-         rhs_parents = []
 +    def generate_revision_id(self, uuid, revnum, path):
 +        return self._generate_revision_id(uuid, revnum, path, self.scheme)
 +
 +    def unprefix(self, branch_path, repos_path):
 +        (bp, np) = self.scheme.unprefix(repos_path)
 +        assert branch_path == bp
 +        return np
 +
 +    def __eq__(self, other):
 +        return type(self) == type(other) and self.scheme == other.scheme
 +
 +
 +class BzrSvnMappingFileProps:
 +    @classmethod
 +    def supports_custom_fileprops(cls):
 +        """Whether this mapping can be used with custom file properties."""
 +        return True
 +
 +    def import_revision(self, svn_revprops, fileprops, rev):
 +        parse_svn_revprops(svn_revprops, rev)
 +        parse_revision_metadata(
 +                fileprops.get(SVN_PROP_BZR_REVISION_INFO, ""), rev)
 +
 +    def get_rhs_parents(self, branch_path, revprops, fileprops):
-         return []
 +        bzr_merges = fileprops.get(SVN_PROP_BZR_ANCESTRY+str(self.scheme), None)
 +        if bzr_merges is not None:
 +            return parse_merge_property(bzr_merges.splitlines()[-1])
 +
++        return ()
 +
 +    def get_rhs_ancestors(self, branch_path, revprops, fileprops):
 +        ancestry = []
 +        for l in fileprops.get(SVN_PROP_BZR_ANCESTRY+str(self.scheme), "").splitlines():
 +            ancestry.extend(l.split("\n"))
 +        return ancestry
 +
 +    def import_fileid_map(self, svn_revprops, fileprops):
 +        fileids = fileprops.get(SVN_PROP_BZR_FILEIDS, None)
 +        if fileids is None:
 +            return {}
 +        return parse_fileid_property(fileids)
 +
 +    def _record_merges(self, merges, fileprops):
 +        """Store the extra merges (non-LHS parents) in a file property.
 +
 +        :param merges: List of parents.
 +        """
 +        # Bazaar Parents
 +        old = fileprops.get(SVN_PROP_BZR_ANCESTRY+str(self.scheme), "")
 +        svnprops = { SVN_PROP_BZR_ANCESTRY+str(self.scheme): old + "\t".join(merges) + "\n" }
 +
 +        return svnprops
 + 
 +    def export_revision(self, branch_root, timestamp, timezone, committer, revprops, revision_id, revno, merges, old_fileprops):
 +        # Keep track of what Subversion properties to set later on
 +        fileprops = {}
 +        fileprops[SVN_PROP_BZR_REVISION_INFO] = generate_revision_metadata(
 +            timestamp, timezone, committer, revprops)
 +
 +        if len(merges) > 0:
 +            fileprops.update(self._record_merges(merges, old_fileprops))
 +
 +        # Set appropriate property if revision id was specified by 
 +        # caller
 +        if revision_id is not None:
 +            old = old_fileprops.get(SVN_PROP_BZR_REVISION_ID+str(self.scheme), "")
 +            fileprops[SVN_PROP_BZR_REVISION_ID+str(self.scheme)] = old + "%d %s\n" % (revno, revision_id)
 +
 +        return ({}, fileprops)
 +
 +    def get_revision_id(self, branch_path, revprops, fileprops):
 +        # Lookup the revision from the bzr:revision-id-vX property
 +        text = fileprops.get(SVN_PROP_BZR_REVISION_ID+str(self.scheme), None)
 +        if text is None:
 +            return (None, None)
 +
 +        lines = text.splitlines()
 +        if len(lines) == 0:
 +            return (None, None)
 +
 +        try:
 +            return parse_revid_property(lines[-1])
 +        except errors.InvalidPropertyValue, e:
 +            mutter(str(e))
 +            return (None, None)
 +
 +    def export_fileid_map(self, fileids, revprops, fileprops):
 +        if fileids != {}:
 +            file_id_text = generate_fileid_property(fileids)
 +            fileprops[SVN_PROP_BZR_FILEIDS] = file_id_text
 +        else:
 +            fileprops[SVN_PROP_BZR_FILEIDS] = ""
 +
 +class BzrSvnMappingv3FileProps(BzrSvnMappingFileProps, BzrSvnMappingv3):
 +    pass
 +
 +class BzrSvnMappingRevProps:
 +    @classmethod
 +    def supports_custom_revprops(cls):
 +        """Whether this mapping can be used with custom revision properties."""
 +        return True
 +
 +    def import_revision(self, svn_revprops, fileprops, rev):
 +        parse_svn_revprops(svn_revprops, rev)
 +        parse_bzr_svn_revprops(svn_revprops, rev)
 +
 +    def import_fileid_map(self, svn_revprops, fileprops):
 +        if not svn_revprops.has_key(SVN_REVPROP_BZR_FILEIDS):
 +            return {}
 +        return parse_fileid_property(svn_revprops[SVN_REVPROP_BZR_FILEIDS])
 +
 +    def get_rhs_parents(self, branch_path, svn_revprops, 
 +                        fileprops):
 +        if svn_revprops[SVN_REVPROP_BZR_ROOT] != branch:
 +            return []
 +        return svn_revprops.get(SVN_REVPROP_BZR_MERGE, "").splitlines()
 +
 +    def get_revision_id(self, branch_path, revprops, fileprops):
 +        if not revprops.has_key(SVN_REVPROP_BZR_MAPPING_VERSION):
 +            return (None, None)
 +        if revprops[SVN_REVPROP_BZR_ROOT] == branch_path:
 +            revid = revprops[SVN_REVPROP_BZR_REVISION_ID]
 +            revno = int(revprops[SVN_REVPROP_BZR_REVNO])
 +            return (revno, revid)
 +        return (None, None)
 +
 +    def export_revision(self, branch_root, timestamp, timezone, committer, 
 +                        revprops, revision_id, revno, merges, 
 +                        fileprops):
 +        svn_revprops = {SVN_REVPROP_BZR_MAPPING_VERSION: str(MAPPING_VERSION)}
 +
 +        if timestamp is not None:
 +            svn_revprops[SVN_REVPROP_BZR_TIMESTAMP] = format_highres_date(timestamp, timezone)
 +
 +        if committer is not None:
 +            svn_revprops[SVN_REVPROP_BZR_COMMITTER] = committer.encode("utf-8")
 +
 +        if revprops is not None:
 +            for name, value in revprops.items():
 +                svn_revprops[SVN_REVPROP_BZR_REVPROP_PREFIX+name] = value
 +
 +        svn_revprops[SVN_REVPROP_BZR_ROOT] = branch_root
 +
 +        if revision_id is not None:
 +            svn_revprops[SVN_REVPROP_BZR_REVISION_ID] = revision_id
 +
 +        if merges != []:
 +            svn_revprops[SVN_REVPROP_BZR_MERGE] = "".join([x+"\n" for x in merges])
 +        svn_revprops[SVN_REVPROP_BZR_REVNO] = str(revno)
 +
 +        return (svn_revprops, {})
 +
 +    def export_fileid_map(self, fileids, revprops, fileprops):
 +        revprops[SVN_REVPROP_BZR_FILEIDS] = generate_fileid_property(fileids)
 +
 +    def get_rhs_ancestors(self, branch_path, revprops, fileprops):
 +        raise NotImplementedError(self.get_rhs_ancestors)
 +
 +
 +class BzrSvnMappingv3RevProps(BzrSvnMappingRevProps, BzrSvnMappingv3):
 +    pass
 +
 +
 +class BzrSvnMappingv4(BzrSvnMappingRevProps):
 +    revid_prefix = "svn-v4"
 +    experimental = True
 +
      @staticmethod
 -    def generate_file_id(uuid, revnum, branch, inv_path):
 -        assert isinstance(uuid, str)
 -        assert isinstance(revnum, int)
 -        assert isinstance(branch, str)
 -        assert isinstance(inv_path, unicode)
 -        inv_path = inv_path.encode("utf-8")
 -        ret = "%d@%s:%s:%s" % (revnum, uuid, escape_svn_path(branch), escape_svn_path(inv_path))
 -        if len(ret) > 150:
 -            ret = "%d@%s:%s;%s" % (revnum, uuid, 
 -                                escape_svn_path(branch),
 -                                sha.new(inv_path).hexdigest())
 -        assert isinstance(ret, str)
 -        return osutils.safe_file_id(ret)
 +    def supports_roundtripping():
 +        return True
 +
 +    @classmethod
 +    def parse_revision_id(cls, revid):
 +        assert isinstance(revid, str)
  
 +        if not revid.startswith(cls.revid_prefix):
 +            raise InvalidRevisionId(revid, "")
 +
 +        try:
 +            (version, uuid, branch_path, srevnum) = revid.split(":")
 +        except ValueError:
 +            raise InvalidRevisionId(revid, "")
 +
 +        branch_path = unescape_svn_path(branch_path)
 +
 +        return (uuid, branch_path, int(srevnum), cls())
 +
 +    def generate_revision_id(self, uuid, revnum, path):
 +        return "svn-v4:%s:%s:%d" % (uuid, path, revnum)
 +
 +    def generate_file_id(self, uuid, revnum, branch, inv_path):
 +        return "%d@%s:%s/%s" % (revnum, uuid, branch, inv_path.encode("utf-8"))
 +
 +    def is_branch(self, branch_path):
 +        return True
 +
 +    def is_tag(self, tag_path):
 +        return True
 +
 +    def __eq__(self, other):
 +        return type(self) == type(other)
 +
 +
 +class BzrSvnMappingv3Hybrid(BzrSvnMappingv3):
 +    def __init__(self, scheme):
 +        BzrSvnMappingv3.__init__(self, scheme)
 +        self.revprops = BzrSvnMappingv3RevProps(scheme)
 +        self.fileprops = BzrSvnMappingv3FileProps(scheme)
 +
 +    def get_rhs_parents(self, branch_path, svn_revprops, fileprops):
 +        if svn_revprops.has_key(SVN_REVPROP_BZR_MAPPING_VERSION):
 +            return self.revprops.get_rhs_parents(branch_path, svn_revprops, fileprops)
 +        else:
 +            return self.fileprops.get_rhs_parents(branch_path, svn_revprops, fileprops)
 +
 +    def get_revision_id(self, branch_path, revprops, fileprops):
 +        if revprops.has_key(SVN_REVPROP_BZR_MAPPING_VERSION):
 +            return self.revprops.get_revision_id(branch_path, revprops, fileprops)
 +        else:
 +            return self.fileprops.get_revision_id(branch_path, revprops, fileprops)
 +
 +    def import_fileid_map(self, svn_revprops, fileprops):
 +        if svn_revprops.has_key(SVN_REVPROP_BZR_MAPPING_VERSION):
 +            return self.revprops.import_fileid_map(svn_revprops, fileprops)
 +        else:
 +            return self.fileprops.import_fileid_map(svn_revprops, fileprops)
 +
 +    def export_revision(self, branch_root, timestamp, timezone, committer, revprops, revision_id, revno, 
 +                        merges, fileprops):
 +        (_, fileprops) = self.fileprops.export_revision(branch_root, timestamp, timezone, committer, 
 +                                      revprops, revision_id, revno, merges, fileprops)
 +        (revprops, _) = self.revprops.export_revision(branch_root, timestamp, timezone, committer, 
 +                                      revprops, revision_id, revno, merges, fileprops)
 +        return (revprops, fileprops)
 +
 +    def export_fileid_map(self, fileids, revprops, fileprops):
 +        self.fileprops.export_fileid_map(fileids, revprops, fileprops)
 +        self.revprops.export_fileid_map(fileids, revprops, fileprops)
 +
 +    def import_revision(self, svn_revprops, fileprops, rev):
 +        self.fileprops.import_revision(svn_revprops, fileprops, rev)
 +        self.revprops.import_revision(svn_revprops, fileprops, rev)
  
  class BzrSvnMappingRegistry(registry.Registry):
 +    """Registry for the various Bzr<->Svn mappings."""
      def register(self, key, factory, help):
          """Register a mapping between Bazaar and Subversion semantics.
  
diff --cc repository.py
index dc5701e22fe5438f6c27098d00bdee271b84a35c,f7161956c370a944953bbf59022f80cd4ab3b16d..7387f1453f61ce9b7f6e10a282897db3aeaa7465
@@@ -425,45 -621,28 +425,45 @@@ class SvnRepository(Repository)
              parents_list.append(parents)
          return parents_list
  
 -    def revision_parents(self, revision_id, bzr_merges=None, svk_merges=None):
 +    def _svk_merged_revisions(self, branch, revnum, mapping, 
 +                              fileprops):
 +        """Find out what SVK features were merged in a revision.
 +
 +        """
 +        current = fileprops.get(SVN_PROP_SVK_MERGE, "")
 +        if current == "":
 +            return
 +        (prev_path, prev_revnum) = self._log.get_previous(branch, revnum)
 +        if prev_path is None and prev_revnum == -1:
 +            previous = ""
 +        else:
 +            previous = self.branchprop_list.get_property(prev_path.encode("utf-8"), 
 +                         prev_revnum, SVN_PROP_SVK_MERGE, "")
 +        for feature in svk_features_merged_since(current, previous):
 +            revid = svk_feature_to_revision_id(feature, mapping)
 +            if revid is not None:
 +                yield revid
 +
 +    def revision_parents(self, revision_id, svn_fileprops=None, 
 +                         svn_revprops=None):
          """See Repository.revision_parents()."""
-         parent_ids = []
+         parent_ids = ()
 -        (branch, revnum, scheme) = self.lookup_revision_id(revision_id)
 -        mainline_parent = self._mainline_revision_parent(branch, revnum, scheme)
 +        (branch, revnum, mapping) = self.lookup_revision_id(revision_id)
 +        mainline_parent = self._mainline_revision_parent(branch, revnum, mapping)
          if mainline_parent is not None:
-             parent_ids.append(mainline_parent)
+             parent_ids += (mainline_parent,)
  
 -        # 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 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 svn_fileprops is None:
 +            svn_fileprops = lazy_dict(lambda: self.branchprop_list.get_changed_properties(branch, revnum))
 +
 +        if svn_revprops is None:
 +            svn_revprops = lazy_dict(lambda: self.transport.revprop_list(revnum))
  
 -        parent_ids += bzr_merges
 +        extra_rhs_parents = mapping.get_rhs_parents(branch, svn_revprops, svn_fileprops)
-         parent_ids.extend(extra_rhs_parents)
++        parent_ids += extra_rhs_parents
  
-         if extra_rhs_parents == []:
-             parent_ids.extend(self._svk_merged_revisions(branch, revnum, mapping, svn_fileprops))
 -        if bzr_merges == ():
 -            # Commit was doing using svk apparently
 -            parent_ids += tuple(svk_merges)
++        if extra_rhs_parents == ():
++            parent_ids += tuple(self._svk_merged_revisions(branch, revnum, mapping, svn_fileprops))
  
          return parent_ids
  
Simple merge
index 33f53d9ff6b82853a45b5ee60de9dea9b5fc5bd1,7758442711140fd726606cc99e4ac4421d0e39e2..298ff6fb306b67756a1ef6214096cee3ff6c427b
@@@ -486,17 -489,15 +486,17 @@@ class TestSubversionRepositoryWorks(Tes
          self.build_tree({'dc/foo': "data2"})
          self.client_commit("dc", "Second Message")
          repository = Repository.open("svn+%s" % repos_url)
-         self.assertEqual([],
 +        mapping = repository.get_mapping()
+         self.assertEqual((),
                  repository.revision_parents(
 -                    repository.generate_revision_id(0, "", "none")))
 -        self.assertEqual((repository.generate_revision_id(0, "", "none"),),
 +                    repository.generate_revision_id(0, "", mapping)))
-         self.assertEqual([repository.generate_revision_id(0, "", mapping)],
++        self.assertEqual((repository.generate_revision_id(0, "", mapping),),
                  repository.revision_parents(
 -                    repository.generate_revision_id(1, "", "none")))
 -        self.assertEqual((repository.generate_revision_id(1, "", "none"),),
 +                    repository.generate_revision_id(1, "", mapping)))
-         self.assertEqual([
-             repository.generate_revision_id(1, "", mapping)],
++        self.assertEqual((
++            repository.generate_revision_id(1, "", mapping),),
              repository.revision_parents(
 -                repository.generate_revision_id(2, "", "none")))
 +                repository.generate_revision_id(2, "", mapping)))
  
      def test_revision_fileidmap(self):
          repos_url = self.make_client('d', 'dc')
          self.client_set_prop("dc", "bzr:ancestry:v3-none", "ghostparent\n")
          self.client_commit("dc", "Second Message")
          repository = Repository.open("svn+%s" % repos_url)
-         self.assertEqual([],
 +        mapping = repository.get_mapping()
+         self.assertEqual((),
                  repository.revision_parents(
 -                    repository.generate_revision_id(0, "", "none")))
 -        self.assertEqual((repository.generate_revision_id(0, "", "none"),),
 +                    repository.generate_revision_id(0, "", mapping)))
-         self.assertEqual([repository.generate_revision_id(0, "", mapping)],
++        self.assertEqual((repository.generate_revision_id(0, "", mapping),),
                  repository.revision_parents(
 -                    repository.generate_revision_id(1, "", "none")))
 -        self.assertEqual((repository.generate_revision_id(1, "", "none"),
 +                    repository.generate_revision_id(1, "", mapping)))
-         self.assertEqual([repository.generate_revision_id(1, "", mapping),
-             "ghostparent"], 
++        self.assertEqual((repository.generate_revision_id(1, "", mapping),
+             "ghostparent"), 
                  repository.revision_parents(
 -                    repository.generate_revision_id(2, "", "none")))
 +                    repository.generate_revision_id(2, "", mapping)))
   
      def test_revision_svk_parent(self):
          repos_url = self.make_client('d', 'dc')
          self.client_set_prop("dc/trunk", "svk:merge", 
              "%s:/branches/foo:1\n" % repository.uuid)
          self.client_commit("dc", "Second Message")
 -        self.assertEqual((repository.generate_revision_id(1, "trunk", "trunk0"),
 -            repository.generate_revision_id(1, "branches/foo", "trunk0")), 
 +        mapping = repository.get_mapping()
-         self.assertEqual([repository.generate_revision_id(1, "trunk", mapping),
-             repository.generate_revision_id(1, "branches/foo", mapping)], 
++        self.assertEqual((repository.generate_revision_id(1, "trunk", mapping),
++            repository.generate_revision_id(1, "branches/foo", mapping)), 
                  repository.revision_parents(
 -                    repository.generate_revision_id(2, "trunk", "trunk0")))
 - 
 +                    repository.generate_revision_id(2, "trunk", mapping)))
      
      def test_get_revision(self):
          repos_url = self.make_client('d', 'dc')
          self.build_tree({'dc/foo': "data2"})
          (num, date, author) = self.client_commit("dc", "Second Message")
          repository = Repository.open("svn+%s" % repos_url)
 +        mapping = repository.get_mapping()
          rev = repository.get_revision(
 -            repository.generate_revision_id(2, "", "none"))
 -        self.assertEqual((repository.generate_revision_id(1, "", "none"),),
 +            repository.generate_revision_id(2, "", mapping))
-         self.assertEqual([repository.generate_revision_id(1, "", mapping)],
++        self.assertEqual((repository.generate_revision_id(1, "", mapping),),
                  rev.parent_ids)
          self.assertEqual(rev.revision_id, 
 -                repository.generate_revision_id(2, "", "none"))
 +                repository.generate_revision_id(2, "", mapping))
          self.assertEqual(author, rev.committer)
          self.assertIsInstance(rev.properties, dict)
  
          self.client_update("dc")
          (num, date, author) = self.client_commit("dc", "Second Message")
          repository = Repository.open("svn+%s" % repos_url)
 -        revid = default_mapping.generate_revision_id(repository.uuid, 2, "", "none")
 +        mapping = repository.get_mapping()
 +        revid = mapping.generate_revision_id(repository.uuid, 2, "")
          rev = repository.get_revision("myrevid")
-         self.assertEqual([repository.generate_revision_id(1, "", mapping)],
 -        self.assertEqual((repository.generate_revision_id(1, "", "none"),),
++        self.assertEqual((repository.generate_revision_id(1, "", mapping),),
                  rev.parent_ids)
          self.assertEqual(rev.revision_id, 
 -                         repository.generate_revision_id(2, "", "none"))
 +                         repository.generate_revision_id(2, "", mapping))
          self.assertEqual(author, rev.committer)
          self.assertIsInstance(rev.properties, dict)
  
@@@ -1175,18 -1147,15 +1175,18 @@@ class TestSvnRevisionTree(TestCaseWithS
          self.assertEqual(self.inventory, self.tree.inventory)
  
      def test_get_parent_ids(self):
 -        self.assertEqual((self.repos.generate_revision_id(0, "", "none"),), self.tree.get_parent_ids())
 +        mapping = self.repos.get_mapping()
-         self.assertEqual([self.repos.generate_revision_id(0, "", mapping)], self.tree.get_parent_ids())
++        self.assertEqual((self.repos.generate_revision_id(0, "", mapping),), self.tree.get_parent_ids())
  
      def test_get_parent_ids_zero(self):
 +        mapping = self.repos.get_mapping()
          tree = self.repos.revision_tree(
 -                self.repos.generate_revision_id(0, "", "none"))
 +                self.repos.generate_revision_id(0, "", mapping))
-         self.assertEqual([], tree.get_parent_ids())
+         self.assertEqual((), tree.get_parent_ids())
  
      def test_get_revision_id(self):
 -        self.assertEqual(self.repos.generate_revision_id(1, "", "none"),
 +        mapping = self.repos.get_mapping()
 +        self.assertEqual(self.repos.generate_revision_id(1, "", mapping),
                           self.tree.get_revision_id())
  
      def test_get_file_lines(self):
index 13c57b376b55164a854267c62f51a0441c29b1d6,8d4df37f41ca70d4426b38320fd5e6fd2035ac86..133f22cffc6c9005f6b8c273a069722253b4270e
@@@ -85,5 -88,65 +85,3 @@@ class TestRevidMap(TestCase)
          revidmap.insert_revid("bla", "mypath", 200, 42, "brainslug")
          self.assertEquals(None, 
                  revidmap.lookup_branch_revnum(42, "mypath", "brainslug"))
 -
 -
 -class TestParseRevisionId(TestCase):
 -    def test_parse_revision_id_unknown(self):
 -        self.assertRaises(InvalidRevisionId, 
 -                lambda: default_mapping.parse_revision_id("bla"))
 -
 -    def test_parse_revision_id(self):
 -        self.assertEquals(("myuuid", "bla", 5, "foobar"), 
 -            default_mapping.parse_revision_id(
 -                default_mapping.generate_revision_id("myuuid", 5, "bla", "foobar")))
 -
 -
 -class RevisionIdMappingTest(TestCase):
 -    def test_generate_revid(self):
 -        self.assertEqual("svn-v%d-undefined:myuuid:branch:5" % MAPPING_VERSION, 
 -                         default_mapping.generate_revision_id("myuuid", 5, "branch", "undefined"))
 -
 -    def test_generate_revid_nested(self):
 -        self.assertEqual("svn-v%d-undefined:myuuid:branch%%2Fpath:5" % MAPPING_VERSION, 
 -                  default_mapping.generate_revision_id("myuuid", 5, "branch/path", "undefined"))
 -
 -    def test_generate_revid_special_char(self):
 -        self.assertEqual("svn-v%d-undefined:myuuid:branch%%2C:5" % MAPPING_VERSION, 
 -             default_mapping.generate_revision_id("myuuid", 5, "branch\x2c", "undefined"))
 -
 -    def test_generate_revid_nordic(self):
 -        self.assertEqual("svn-v%d-undefined:myuuid:branch%%C3%%A6:5" % MAPPING_VERSION, 
 -             default_mapping.generate_revision_id("myuuid", 5, u"branch\xe6".encode("utf-8"), "undefined"))
 -
 -    def test_parse_revid_simple(self):
 -        self.assertEqual(("uuid", "", 4, None),
 -                         default_mapping.parse_revision_id(
 -                             "svn-v%d-undefined:uuid::4" % MAPPING_VERSION))
 -
 -    def test_parse_revid_nested(self):
 -        self.assertEqual(("uuid", "bp/data", 4, None),
 -                         default_mapping.parse_revision_id(
 -                     "svn-v%d-undefined:uuid:bp%%2Fdata:4" % MAPPING_VERSION))
 -
 -    def test_parse_svk_feature_root(self):
 -        self.assertEqual(("auuid", "", 6), 
 -                 parse_svk_feature("auuid:/:6"))
 -
 -    def test_svk_revid_map_nested(self):
 -        self.assertEqual(("auuid", "bp", 6),
 -                         parse_svk_feature("auuid:/bp:6"))
 -
 -    def test_revid_svk_map(self):
 -        self.assertEqual("auuid:/:6", 
 -              revision_id_to_svk_feature("svn-v%d-undefined:auuid::6" % MAPPING_VERSION))
 -
 -    def test_parse_merge_space(self):
 -        self.assertEqual((), parse_merge_property("bla bla"))
 -
 -    def test_parse_merge_empty(self):
 -        self.assertEqual((), parse_merge_property(""))
 -
 -    def test_parse_merge_simple(self):
 -        self.assertEqual(("bla", "bloe"), parse_merge_property("bla\tbloe"))
--
--