]> arthur.barton.de Git - bup.git/blobdiff - lib/bup/vfs.py
Retrieve the dates for all branches with one bulk git call in the VFS.
[bup.git] / lib / bup / vfs.py
index 454355337c4b06bde89f231657be66647a7fea14..301d14572ef6ae5e3817f3ae4d0c791b35bf56ba 100644 (file)
@@ -4,8 +4,9 @@ The vfs.py library makes it possible to expose contents from bup's repository
 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
@@ -162,7 +163,7 @@ class _FileReader(object):
         pass
 
 
-class Node:
+class Node(object):
     """Base class for file representation."""
     def __init__(self, parent, name, mode, hash):
         self.parent = parent
@@ -171,9 +172,19 @@ class Node:
         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())
@@ -289,6 +300,21 @@ class Node:
         """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."""
@@ -375,6 +401,25 @@ class FakeSymlink(Symlink):
 
 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'))
@@ -385,6 +430,11 @@ class Dir(Node):
             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:
@@ -396,6 +446,22 @@ class Dir(Node):
             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.
@@ -457,7 +523,7 @@ class TagDir(Node):
         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)
@@ -480,6 +546,7 @@ class BranchList(Node):
         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)
@@ -494,14 +561,12 @@ class BranchList(Node):
                 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):
@@ -525,10 +590,13 @@ 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