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