and abstracts internal name mangling and storage from the exposition layer.
"""
import os, re, stat, time
-from bup import git
+from bup import git, metadata
from helpers import *
+from bup.git import BUP_NORMAL, BUP_CHUNKED
from bup.hashsplit import GIT_MODE_TREE, GIT_MODE_FILE
EMPTY_SHA='\0'*20
pass
-class Node:
+class Node(object):
"""Base class for file representation."""
def __init__(self, parent, name, mode, hash):
self.parent = parent
self.hash = hash
self.ctime = self.mtime = self.atime = 0
self._subs = None
+ self._metadata = None
def __repr__(self):
- return "<bup.vfs.Node object at X - name:%r hash:%s parent:%r>" \
- % (self.name, self.hash.encode('hex'),
- self.parent.name if self.parent.name else None)
+ return "<%s object at %s - name:%r hash:%s parent:%r>" \
+ % (self.__class__, hex(id(self)),
+ self.name, self.hash.encode('hex'),
+ self.parent.name if self.parent else None)
def __cmp__(a, b):
if a is b:
"""Open the current node. It is an error to open a non-file node."""
raise NotFile('%s is not a regular file' % self.name)
+ def _populate_metadata(self, force=False):
+ # Only Dirs contain .bupm files, so by default, do nothing.
+ pass
+
+ def metadata(self):
+ """Return this Node's Metadata() object, if any."""
+ if not self._metadata and self.parent:
+ self.parent._populate_metadata(force=True)
+ return self._metadata
+
+ def release(self):
+ """Release resources that can be automatically restored (at a cost)."""
+ self._metadata = None
+ self._subs = None
+
class File(Node):
"""A normal file from bup's repository."""
class Dir(Node):
"""A directory stored inside of bup's repository."""
- def __init__(self, *args):
- Node.__init__(self, *args)
- self._metadata_sha = None
+ def __init__(self, *args, **kwargs):
+ Node.__init__(self, *args, **kwargs)
+ self._bupm = None
+
+ def _populate_metadata(self, force=False):
+ if self._metadata and not force:
+ return
+ if not self._subs:
+ self._mksubs()
+ if not self._bupm:
+ return
+ meta_stream = self._bupm.open()
+ dir_meta = metadata.Metadata.read(meta_stream)
+ for sub in self:
+ if not stat.S_ISDIR(sub.mode):
+ sub._metadata = metadata.Metadata.read(meta_stream)
+ self._metadata = dir_meta
def _mksubs(self):
self._subs = {}
assert(type == 'tree')
for (mode,mangled_name,sha) in git.tree_decode(''.join(it)):
if mangled_name == '.bupm':
- self._metadata_sha = sha
+ bupmode = stat.S_ISDIR(mode) and BUP_CHUNKED or BUP_NORMAL
+ self._bupm = File(self, mangled_name, GIT_MODE_FILE, sha,
+ bupmode)
continue
name = mangled_name
(name,bupmode) = git.demangle_name(mangled_name)
else:
self._subs[name] = File(self, name, mode, sha, bupmode)
+ def metadata(self):
+ """Return this Dir's Metadata() object, if any."""
+ self._populate_metadata()
+ return self._metadata
+
+ def metadata_file(self):
+ """Return this Dir's .bupm File, if any."""
+ if not self._subs:
+ self._mksubs()
+ return self._bupm
+
+ def release(self):
+ """Release restorable resources held by this node."""
+ self._bupm = None
+ super(Dir, self).release()
+
class CommitDir(Node):
"""A directory that contains all commits that are reachable by a ref.
for (name, sha) in git.list_refs():
if name.startswith('refs/tags/'):
name = name[10:]
- date = git.rev_get_date(sha.encode('hex'))
+ date = git.get_commit_dates([sha.encode('hex')])[0]
commithex = sha.encode('hex')
target = '../.commit/%s/%s' % (commithex[:2], commithex[2:])
tag1 = FakeSymlink(self, name, target)
tag_dir = TagDir(self, '.tag')
self._subs['.tag'] = tag_dir
- for (name,sha) in git.list_refs():
- if name.startswith('refs/heads/'):
- name = name[11:]
- date = git.rev_get_date(sha.encode('hex'))
- n1 = BranchList(self, name, sha)
- n1.ctime = n1.mtime = date
- self._subs[name] = n1
+ refs_info = [(name[11:], sha) for (name,sha) in git.list_refs() \
+ if name.startswith('refs/heads/')]
+
+ dates = git.get_commit_dates([sha.encode('hex')
+ for (name, sha) in refs_info])
+
+ for (name, sha), date in zip(refs_info, dates):
+ n1 = BranchList(self, name, sha)
+ n1.ctime = n1.mtime = date
+ self._subs[name] = n1