]> arthur.barton.de Git - bup.git/blobdiff - lib/bup/index.py
Add docstrings to lib/bup/helpers.py
[bup.git] / lib / bup / index.py
index 39cae888ed04cbf5ec04f12025314d7a469723be..bc057f90ec5940e28bcd1908b0403da7cdf0df6a 100644 (file)
@@ -9,8 +9,9 @@ ENTLEN = struct.calcsize(INDEX_SIG)
 FOOTER_SIG = '!Q'
 FOOTLEN = struct.calcsize(FOOTER_SIG)
 
-IX_EXISTS = 0x8000
-IX_HASHVALID = 0x4000
+IX_EXISTS = 0x8000        # file exists on filesystem
+IX_HASHVALID = 0x4000     # the stored sha1 matches the filesystem
+IX_SHAMISSING = 0x2000    # the stored sha1 object doesn't seem to exist
 
 class Error(Exception):
     pass
@@ -41,6 +42,7 @@ def _golevel(level, f, ename, newentry):
     assert(level)
     while ename[:len(level.ename)] != level.ename:
         n = BlankNewEntry(level.ename[-1])
+        n.flags |= IX_EXISTS
         (n.children_ofs,n.children_n) = level.write(f)
         level.parent.list.append(n)
         level = level.parent
@@ -115,6 +117,9 @@ class Entry:
     def exists(self):
         return not self.is_deleted()
 
+    def sha_missing(self):
+        return (self.flags & IX_SHAMISSING) or not (self.flags & IX_HASHVALID)
+
     def is_deleted(self):
         return (self.flags & IX_EXISTS) == 0
 
@@ -166,6 +171,24 @@ class ExistingEntry(Entry):
          self.flags, self.children_ofs, self.children_n
          ) = struct.unpack(INDEX_SIG, str(buffer(m, ofs, ENTLEN)))
 
+    # effectively, we don't bother messing with IX_SHAMISSING if
+    # not IX_HASHVALID, since it's redundant, and repacking is more
+    # expensive than not repacking.
+    # This is implemented by having sha_missing() check IX_HASHVALID too.
+    def set_sha_missing(self, val):
+        val = val and 1 or 0
+        oldval = self.sha_missing() and 1 or 0
+        if val != oldval:
+            flag = val and IX_SHAMISSING or 0
+            newflags = (self.flags & (~IX_SHAMISSING)) | flag
+            self.flags = newflags
+            self.repack()
+
+    def unset_sha_missing(self, flag):
+        if self.flags & IX_SHAMISSING:
+            self.flags &= ~IX_SHAMISSING
+            self.repack()
+
     def repack(self):
         self._m[self._ofs:self._ofs+ENTLEN] = self.packed()
         if self.parent and not self.is_valid():
@@ -270,6 +293,7 @@ class Reader:
     def close(self):
         self.save()
         if self.writable and self.m:
+            self.m.close()
             self.m = None
             self.writable = False
 
@@ -281,6 +305,17 @@ class Reader:
                 yield (name, e)
 
 
+# FIXME: this function isn't very generic, because it splits the filename
+# in an odd way and depends on a terminating '/' to indicate directories.
+def pathsplit(p):
+    """Split a path into a list of elements of the file system hierarchy."""
+    l = p.split('/')
+    l = [i+'/' for i in l[:-1]] + l[-1:]
+    if l[-1] == '':
+        l.pop()  # extra blank caused by terminating '/'
+    return l
+
+
 class Writer:
     def __init__(self, filename):
         self.rootlevel = self.level = Level([], None)
@@ -369,14 +404,10 @@ def reduce_paths(paths):
     xpaths = []
     for p in paths:
         rp = realpath(p)
-        try:
-            st = os.lstat(rp)
-            if stat.S_ISDIR(st.st_mode):
-                rp = slashappend(rp)
-                p = slashappend(p)
-        except OSError, e:
-            if e.errno != errno.ENOENT:
-                raise
+        st = os.lstat(rp)
+        if stat.S_ISDIR(st.st_mode):
+            rp = slashappend(rp)
+            p = slashappend(p)
         xpaths.append((rp, p))
     xpaths.sort()