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 "<%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):
- return cmp(a and a.name or None, b and b.name or None)
+ if a is b:
+ return 0
+ return (cmp(a and a.parent, b and b.parent) or
+ cmp(a and a.name, b and b.name))
def __iter__(self):
return iter(self.subs())
"""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, **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 = {}
it = cp().get(self.hash.encode('hex'))
type = it.next()
assert(type == 'tree')
for (mode,mangled_name,sha) in git.tree_decode(''.join(it)):
+ if mangled_name == '.bupm':
+ 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)
if bupmode == git.BUP_CHUNKED:
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)
tags = git.tags()
revs = list(git.rev_list(self.hash.encode('hex')))
+ latest = revs[0]
for (date, commit) in revs:
l = time.localtime(date)
ls = time.strftime('%Y-%m-%d-%H%M%S', l)
t1.ctime = t1.mtime = date
self._subs[tag] = t1
- latest = max(revs)
- if latest:
- (date, commit) = latest
- commithex = commit.encode('hex')
- target = '../.commit/%s/%s' % (commithex[:2], commithex[2:])
- n1 = FakeSymlink(self, 'latest', target)
- n1.ctime = n1.mtime = date
- self._subs['latest'] = n1
+ (date, commit) = latest
+ commithex = commit.encode('hex')
+ target = '../.commit/%s/%s' % (commithex[:2], commithex[2:])
+ n1 = FakeSymlink(self, 'latest', target)
+ n1.ctime = n1.mtime = date
+ self._subs['latest'] = n1
class RefList(Node):
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