Rev 458: merge marshalling code. in file:///home/jelmer/bzr-svn/native/

Jelmer Vernooij jelmer at samba.org
Sun May 20 14:45:16 BST 2007


At file:///home/jelmer/bzr-svn/native/

------------------------------------------------------------
revno: 458
revision-id: jelmer at samba.org-20070520134515-jngcnfq2hvdrd7f2
parent: jelmer at samba.org-20070519001208-9l1dyi9ny1s19gak
parent: jelmer at samba.org-20070122183621-rsper7qlcw3v9i1k
committer: Jelmer Vernooij <jelmer at samba.org>
branch nick: native
timestamp: Sun 2007-05-20 14:45:15 +0100
message:
  merge marshalling code.
added:
  marshall.py                    marshall.py-20070122183508-nii0uu1od8pi5u2f-1
  tests/test_marshall.py         test_marshall.py-20070122183413-0up9hxu73ral1ven-1
  transport_svn.py               transport_svn.py-20070117114238-elvrsw9ohhz0rtz4-1
modified:
  tests/__init__.py              __init__.py-20060508151940-e9f4d914801a2535
    ------------------------------------------------------------
    revno: 412.1.1
    merged: jelmer at samba.org-20070122183621-rsper7qlcw3v9i1k
    parent: jelmer at samba.org-20070122105054-po2g4eoaytunwege
    committer: Jelmer Vernooij <jelmer at samba.org>
    branch nick: native
    timestamp: Mon 2007-01-22 19:36:21 +0100
    message:
      Import marshalling code from bzrsvnserve
=== added file 'marshall.py'
--- a/marshall.py	1970-01-01 00:00:00 +0000
+++ b/marshall.py	2007-01-22 18:36:21 +0000
@@ -0,0 +1,110 @@
+# Copyright (C) 2006-2007 Jelmer Vernooij <jelmer at samba.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+from bzrlib.errors import BzrError
+
+class literal:
+    def __init__(self, txt):
+        self.txt = txt
+
+    def __str__(self):
+        return self.txt
+
+    def __repr__(self):
+        return self.txt
+
+# 1. Syntactic structure
+# ----------------------
+# 
+# The Subversion protocol is specified in terms of the following
+# syntactic elements, specified using ABNF [RFC 2234]:
+# 
+#   item   = word / number / string / list
+#   word   = ALPHA *(ALPHA / DIGIT / "-") space
+#   number = 1*DIGIT space
+#   string = 1*DIGIT ":" *OCTET space
+#          ; digits give the byte count of the *OCTET portion
+#   list   = "(" space *item ")" space
+#   space  = 1*(SP / LF)
+# 
+
+class MarshallError(BzrError):
+    def __init__(self, msg):
+        BzrError.__init__(self, msg)
+
+
+def marshall(x):
+    if isinstance(x, int):
+        return "%d " % x
+    elif isinstance(x, list) or isinstance(x, tuple):
+        return "( " + "".join(map(marshall, x)) + ") "
+    elif isinstance(x, literal):
+        return "%s " % x
+    elif isinstance(x, basestring):
+        return "%d:%s " % (len(x), x)
+    raise MarshallError("Unable to marshall type %s" % x)
+
+
+def unmarshall(x):
+    whitespace = ['\n', ' ']
+    if len(x) == 0:
+        raise MarshallError("Not enough data")
+    if x[0] == "(" and x[1] == " ": # list follows
+        x = x[2:]
+        ret = []
+        try:
+            while x[0] != ")":
+                (x, n) = unmarshall(x)
+                ret.append(n)
+        except IndexError:
+            raise MarshallError("List not terminated")
+        
+        if not x[1] in whitespace:
+            raise MarshallError("Expected space, got %c" % x[1])
+
+        return (x[2:], ret)
+    elif x[0].isdigit():
+        num = ""
+        # Check if this is a string or a number
+        while x[0].isdigit():
+            num += x[0]
+            x = x[1:]
+        num = int(num)
+
+        if x[0] in whitespace:
+            return (x[1:], num)
+        elif x[0] == ":":
+            if len(x) < num:
+                raise MarshallError("Expected string of length %r" % num)
+            return (x[num+2:], x[1:num+1])
+        else:
+            raise MarshallError("Expected whitespace or ':', got '%c" % x[0])
+    elif x[0].isalpha():
+        ret = ""
+        # Parse literal
+        try:
+            while x[0].isalpha() or x[0].isdigit() or x[0] == '-':
+                ret += x[0]
+                x = x[1:]
+        except IndexError:
+            raise MarshallError("Expected literal")
+
+        if not x[0] in whitespace:
+            raise MarshallError("Expected whitespace, got %c" % x[0])
+
+        return (x[1:], ret)
+    else:
+        raise MarshallError("Unexpected character '%c'" % x[0])

=== added file 'tests/test_marshall.py'
--- a/tests/test_marshall.py	1970-01-01 00:00:00 +0000
+++ b/tests/test_marshall.py	2007-01-22 18:36:21 +0000
@@ -0,0 +1,87 @@
+# Copyright (C) 2006-2007 Jelmer Vernooij <jelmer at samba.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+from bzrlib.tests import TestCase
+from marshall import literal, MarshallError, marshall, unmarshall
+
+class TestMarshalling(TestCase):
+    def test_literal_txt(self):
+        l = literal("foo")
+        self.assertEqual("foo", l.txt)
+
+    def test_literal_str(self):
+        l = literal("foo bar")
+        self.assertEqual("foo bar", l.__str__())
+
+    def test_literal_rep(self):
+        l = literal("foo bar")
+        self.assertEqual("foo bar", l.__repr__())
+
+    def test_marshall_error(self):
+        e = MarshallError("bla bla")
+        self.assertEqual("bla bla", e.__str__())
+    
+    def test_marshall_int(self):
+        self.assertEqual("1 ", marshall(1))
+
+    def test_marshall_list(self):
+        self.assertEqual("( 1 2 3 4 ) ", marshall([1,2,3,4]))
+    
+    def test_marshall_list_mixed(self):
+        self.assertEqual("( 1 3 4 3:str ) ", marshall([1,3,4,"str"]))
+
+    def test_marshall_literal(self):
+        self.assertEqual("foo ", marshall(literal("foo")))
+
+    def test_marshall_string(self):
+        self.assertEqual("3:foo ", marshall("foo"))
+
+    def test_marshall_raises(self):
+        self.assertRaises(MarshallError, marshall, dict())
+
+    def test_marshall_list_nested(self):
+        self.assertEqual("( ( ( 3 ) 4 ) ) ", marshall([[[3], 4]]))
+
+    def test_marshall_string_space(self):
+        self.assertEqual("5:bla l ", marshall("bla l"))
+
+    def test_unmarshall_string(self):
+        self.assertEqual(('', "bla l"), unmarshall("5:bla l"))
+
+    def test_unmarshall_list(self):
+        self.assertEqual(('', [4,5]), unmarshall("( 4 5 ) "))
+
+    def test_unmarshall_int(self):
+        self.assertEqual(('', 2), unmarshall("2 "))
+
+    def test_unmarshall_literal(self):
+        self.assertEqual(('', literal("x")), unmarshall("x "))
+
+    def test_unmarshall_empty(self):
+        self.assertRaises(MarshallError, unmarshall, "")
+
+    def test_unmarshall_nospace(self):
+        self.assertRaises(MarshallError, unmarshall, "nospace")
+
+    def test_unmarshall_toolong(self):
+        self.assertRaises(MarshallError, unmarshall, "43432432:bla")
+
+    def test_unmarshall_literal(self):
+        self.assertRaises(MarshallError, unmarshall, ":-3213")
+
+    def test_unmarshall_open_list(self):
+        self.assertRaises(MarshallError, unmarshall, "( 3 4 ")
+

=== added file 'transport_svn.py'
--- a/transport_svn.py	1970-01-01 00:00:00 +0000
+++ b/transport_svn.py	2007-01-22 18:36:21 +0000
@@ -0,0 +1,189 @@
+# Copyright (C) 2006-2007 Jelmer Vernooij <jelmer at samba.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+from transport import SvnRaTransport
+
+import urllib
+
+class SvnRaTransportSvn(SvnRaTransport):
+    """Fake transport for Subversion-related namespaces.
+    
+    This implements just as much of Transport as is necessary 
+    to fool Bazaar. """
+    def __init__(self, url=""):
+        self.pool = Pool()
+        self.is_locked = False
+        # FIXME: Parse URL
+        
+        # FIXME: Connect to 
+        bzr_url = url
+        self.svn_url = bzr_to_svn_url(url)
+        Transport.__init__(self, bzr_url)
+
+        try:
+            mutter('opening SVN RA connection to %r' % self.svn_url)
+            self._ra = svn.client.open_ra_session(self.svn_url.encode('utf8'), 
+                    self._client, self.pool)
+        except SubversionException, (msg, num):
+            if num in (svn.core.SVN_ERR_RA_ILLEGAL_URL, \
+                       svn.core.SVN_ERR_RA_LOCAL_REPOS_OPEN_FAILED, \
+                       svn.core.SVN_ERR_BAD_URL):
+                raise NotBranchError(path=url)
+            raise
+
+    def lock(self):
+        assert (not self.is_locked)
+        self.is_locked = True
+
+    def unlock(self):
+        assert self.is_locked
+        self.is_locked = False
+
+    def has(self, relpath):
+        """See Transport.has()."""
+        # TODO: Raise TransportNotPossible here instead and 
+        # catch it in bzrdir.py
+        return False
+
+    def get(self, relpath):
+        """See Transport.get()."""
+        # TODO: Raise TransportNotPossible here instead and 
+        # catch it in bzrdir.py
+        raise NoSuchFile(path=relpath)
+
+    def stat(self, relpath):
+        """See Transport.stat()."""
+        raise TransportNotPossible('stat not supported on Subversion')
+
+    @need_lock
+    def get_uuid(self):
+        mutter('svn get-uuid')
+        return svn.ra.get_uuid(self._ra)
+
+    @need_lock
+    def get_repos_root(self):
+        mutter("svn get-repos-root")
+        return svn.ra.get_repos_root(self._ra)
+
+    @need_lock
+    def get_latest_revnum(self):
+        mutter("svn get-latest-revnum")
+        return svn.ra.get_latest_revnum(self._ra)
+
+    @need_lock
+    def do_switch(self, switch_rev, switch_target, recurse, switch_url, *args, **kwargs):
+        mutter('svn switch -r %d %r -> %r' % (switch_rev, switch_target, switch_url))
+        return svn.ra.do_switch(self._ra, switch_rev, switch_target, recurse, switch_url, *args, **kwargs)
+
+    @need_lock
+    def get_log(self, path, from_revnum, to_revnum, *args, **kwargs):
+        mutter('svn log %r:%r %r' % (from_revnum, to_revnum, path))
+        return svn.ra.get_log(self._ra, [path], from_revnum, to_revnum, *args, **kwargs)
+
+    @need_lock
+    def reparent(self, url):
+        url = url.rstrip("/")
+        if url == self.svn_url:
+            return
+        self.base = url
+        self.svn_url = url
+        if hasattr(svn.ra, 'reparent'):
+            mutter('svn reparent %r' % url)
+            svn.ra.reparent(self._ra, url, self.pool)
+        else:
+            self._ra = svn.client.open_ra_session(self.svn_url.encode('utf8'), 
+                    self._client, self.pool)
+    @need_lock
+    def get_dir(self, path, revnum, pool=None, kind=False):
+        mutter("svn ls -r %d '%r'" % (revnum, path))
+        path = path.rstrip("/")
+        # ra_dav backends fail with strange errors if the path starts with a 
+        # slash while other backends don't.
+        assert len(path) == 0 or path[0] != "/"
+        if hasattr(svn.ra, 'get_dir2'):
+            fields = 0
+            if kind:
+                fields += svn.core.SVN_DIRENT_KIND
+            return svn.ra.get_dir2(self._ra, path, revnum, fields)
+        else:
+            return svn.ra.get_dir(self._ra, path, revnum)
+
+    def list_dir(self, relpath):
+        assert len(relpath) == 0 or relpath[0] != "/"
+        if relpath == ".":
+            relpath = ""
+        try:
+            (dirents, _, _) = self.get_dir(relpath.rstrip("/"), 
+                                           self.get_latest_revnum())
+        except SubversionException, (msg, num):
+            if num == svn.core.SVN_ERR_FS_NOT_DIRECTORY:
+                raise NoSuchFile(relpath)
+            raise
+        return dirents.keys()
+
+    @need_lock
+    def check_path(self, path, revnum, *args, **kwargs):
+        assert len(path) == 0 or path[0] != "/"
+        mutter("svn check_path -r%d %s" % (revnum, path))
+        return svn.ra.check_path(self._ra, path, revnum, *args, **kwargs)
+
+    @need_lock
+    def mkdir(self, relpath, mode=None):
+        path = "%s/%s" % (self.svn_url, relpath)
+        try:
+            svn.client.mkdir([path.encode("utf-8")], self._client)
+        except SubversionException, (msg, num):
+            if num == svn.core.SVN_ERR_FS_NOT_FOUND:
+                raise NoSuchFile(path)
+            if num == svn.core.SVN_ERR_FS_ALREADY_EXISTS:
+                raise FileExists(path)
+            raise
+
+    @need_lock
+    def do_update(self, revnum, path, *args, **kwargs):
+        mutter('svn update -r %r %r' % (revnum, path))
+        return svn.ra.do_update(self._ra, revnum, path, *args, **kwargs)
+
+    @need_lock
+    def get_commit_editor(self, *args, **kwargs):
+        return svn.ra.get_commit_editor(self._ra, *args, **kwargs)
+
+    def listable(self):
+        """See Transport.listable().
+        """
+        return True
+
+    # There is no real way to do locking directly on the transport 
+    # nor is there a need to as the remote server will take care of 
+    # locking
+    class PhonyLock:
+        def unlock(self):
+            pass
+
+    def lock_write(self, relpath):
+        """See Transport.lock_write()."""
+        return self.PhonyLock()
+
+    def lock_read(self, relpath):
+        """See Transport.lock_read()."""
+        return self.PhonyLock()
+
+    def clone(self, offset=None):
+        """See Transport.clone()."""
+        if offset is None:
+            return SvnRaTransport(self.base)
+
+        return SvnRaTransport(urlutils.join(self.base, offset))

=== modified file 'tests/__init__.py'
--- a/tests/__init__.py	2007-05-17 19:04:30 +0000
+++ b/tests/__init__.py	2007-05-20 13:45:15 +0000
@@ -262,6 +262,7 @@
             'test_errors',
             'test_fileids', 
             'test_logwalker',
+            'test_marshall',
             'test_push',
             'test_radir',
             'test_repos', 




More information about the bazaar-commits mailing list