]> arthur.barton.de Git - bup.git/blobdiff - lib/bup/vfs.py
bup.midx: accommodate python 3
[bup.git] / lib / bup / vfs.py
index e3ea16ffae505d95c28a8d61ad7238f98730a94c..3c97d21c7943166f4094e7a2eb285b42f93e488b 100644 (file)
@@ -53,20 +53,51 @@ from itertools import chain, dropwhile, groupby, tee
 from random import randrange
 from stat import S_IFDIR, S_IFLNK, S_IFREG, S_ISDIR, S_ISLNK, S_ISREG
 from time import localtime, strftime
-import exceptions, re, sys
+import re, sys
 
-from bup import client, git, metadata
-from bup.compat import range
+from bup import git, metadata, vint
+from bup.compat import hexstr, range
 from bup.git import BUP_CHUNKED, cp, get_commit_items, parse_commit, tree_decode
 from bup.helpers import debug2, last
 from bup.metadata import Metadata
+from bup.vint import read_bvec, write_bvec
+from bup.vint import read_vint, write_vint
+from bup.vint import read_vuint, write_vuint
 
+if sys.version_info[0] < 3:
+    from exceptions import IOError as py_IOError
+else:
+    py_IOError = IOError
 
-class IOError(exceptions.IOError):
+# We currently assume that it's always appropriate to just forward IOErrors
+# to a remote client.
+
+class IOError(py_IOError):
     def __init__(self, errno, message, terminus=None):
-        exceptions.IOError.__init__(self, errno, message)
+        py_IOError.__init__(self, errno, message)
         self.terminus = terminus
 
+def write_ioerror(port, ex):
+    assert isinstance(ex, IOError)
+    write_vuint(port,
+                (1 if ex.errno is not None else 0)
+                | (2 if ex.message is not None else 0)
+                | (4 if ex.terminus is not None else 0))
+    if ex.errno is not None:
+        write_vint(port, ex.errno)
+    if ex.message is not None:
+        write_bvec(port, ex.message.encode('utf-8'))
+    if ex.terminus is not None:
+        write_resolution(port, ex.terminus)
+
+def read_ioerror(port):
+    mask = read_vuint(port)
+    no = read_vint(port) if 1 & mask else None
+    msg = read_bvec(port).decode('utf-8') if 2 & mask else None
+    term = read_resolution(port) if 4 & mask else None
+    return IOError(errno=no, message=msg, terminus=term)
+
+
 default_file_mode = S_IFREG | 0o644
 default_dir_mode = S_IFDIR | 0o755
 default_symlink_mode = S_IFLNK | 0o755
@@ -244,6 +275,93 @@ Commit = namedtuple('Commit', ('meta', 'oid', 'coid'))
 item_types = frozenset((Item, Chunky, Root, Tags, RevList, Commit))
 real_tree_types = frozenset((Item, Commit))
 
+def write_item(port, item):
+    kind = type(item)
+    name = bytes(kind.__name__)
+    meta = item.meta
+    has_meta = 1 if isinstance(meta, Metadata) else 0
+    if kind in (Item, Chunky, RevList):
+        assert len(item.oid) == 20
+        if has_meta:
+            vint.send(port, 'sVs', name, has_meta, item.oid)
+            Metadata.write(meta, port, include_path=False)
+        else:
+            vint.send(port, 'sVsV', name, has_meta, item.oid, item.meta)
+    elif kind in (Root, Tags):
+        if has_meta:
+            vint.send(port, 'sV', name, has_meta)
+            Metadata.write(meta, port, include_path=False)
+        else:
+            vint.send(port, 'sVV', name, has_meta, item.meta)
+    elif kind == Commit:
+        assert len(item.oid) == 20
+        assert len(item.coid) == 20
+        if has_meta:
+            vint.send(port, 'sVss', name, has_meta, item.oid, item.coid)
+            Metadata.write(meta, port, include_path=False)
+        else:
+            vint.send(port, 'sVssV', name, has_meta, item.oid, item.coid,
+                      item.meta)
+    elif kind == FakeLink:
+        if has_meta:
+            vint.send(port, 'sVs', name, has_meta, item.target)
+            Metadata.write(meta, port, include_path=False)
+        else:
+            vint.send(port, 'sVsV', name, has_meta, item.target, item.meta)
+    else:
+        assert False
+
+def read_item(port):
+    def read_m(port, has_meta):
+        if has_meta:
+            m = Metadata.read(port)
+            return m
+        return read_vuint(port)
+    kind, has_meta = vint.recv(port, 'sV')
+    if kind == b'Item':
+        oid, meta = read_bvec(port), read_m(port, has_meta)
+        return Item(oid=oid, meta=meta)
+    if kind == b'Chunky':
+        oid, meta = read_bvec(port), read_m(port, has_meta)
+        return Chunky(oid=oid, meta=meta)
+    if kind == b'RevList':
+        oid, meta = read_bvec(port), read_m(port, has_meta)
+        return RevList(oid=oid, meta=meta)
+    if kind == b'Root':
+        return Root(meta=read_m(port, has_meta))
+    if kind == b'Tags':
+        return Tags(meta=read_m(port, has_meta))
+    if kind == b'Commit':
+        oid, coid = vint.recv(port, 'ss')
+        meta = read_m(port, has_meta)
+        return Commit(oid=oid, coid=coid, meta=meta)
+    if kind == b'FakeLink':
+        target, meta = read_bvec(port), read_m(port, has_meta)
+        return FakeLink(target=target, meta=meta)
+    assert False
+
+def write_resolution(port, resolution):
+    write_vuint(port, len(resolution))
+    for name, item in resolution:
+        write_bvec(port, name)
+        if item:
+            port.write(b'\1')
+            write_item(port, item)
+        else:
+            port.write(b'\0')
+
+def read_resolution(port):
+    n = read_vuint(port)
+    result = []
+    for i in range(n):
+        name = read_bvec(port)
+        have_item = ord(port.read(1))
+        assert have_item in (0, 1)
+        item = read_item(port) if have_item else None
+        result.append((name, item))
+    return tuple(result)
+
+
 _root = Root(meta=default_dir_mode)
 _tags = Tags(meta=default_dir_mode)
 
@@ -367,7 +485,7 @@ def tree_data_and_bupm(repo, oid):
         data = ''.join(it)
         assert item_t == 'tree'
     elif item_t != 'tree':
-        raise Exception('%r is not a tree or commit' % oid.encode('hex'))
+        raise Exception('%s is not a tree or commit' % hexstr(oid))
     for _, mangled_name, sub_oid in tree_decode(data):
         if mangled_name == '.bupm':
             return data, sub_oid
@@ -394,12 +512,12 @@ def readlink(repo, item):
     target from the repository if necessary."""
     assert repo
     assert S_ISLNK(item_mode(item))
+    if isinstance(item, FakeLink):
+        return item.target
     if isinstance(item.meta, Metadata):
         target = item.meta.symlink_target
         if target:
             return target
-    elif isinstance(item, FakeLink):
-        return item.target
     return _readlink(repo, item.oid)
 
 def _compute_item_size(repo, item):
@@ -408,6 +526,8 @@ def _compute_item_size(repo, item):
         size = _normal_or_chunked_file_size(repo, item.oid)
         return size
     if S_ISLNK(mode):
+        if isinstance(item, FakeLink):
+            return len(item.target)
         return len(_readlink(repo, item.oid))
     return 0
 
@@ -940,6 +1060,10 @@ def resolve(repo, path, parent=None, want_meta=True, follow=True):
     needed, make a copy via item.meta.copy() and modify that instead.
 
     """
+    if repo.is_remote():
+        # Redirect to the more efficient remote version
+        return repo.resolve(path, parent=parent, want_meta=want_meta,
+                            follow=follow)
     result = _resolve_path(repo, path, parent=parent, want_meta=want_meta,
                            follow=follow)
     _, leaf_item = result[-1]