Fix flags data in index.
[jelmer/dulwich-libgit2.git] / dulwich / index.py
index 3e44e5fd082879464830bb38a0437c55dbf78bff..c238de11df238c7397963688b01853c2f36cf4a8 100644 (file)
@@ -1,5 +1,5 @@
 # index.py -- File parser/write for the git index file
-# Copryight (C) 2008 Jelmer Vernooij <jelmer@samba.org>
+# Copyright (C) 2008-2009 Jelmer Vernooij <jelmer@samba.org>
  
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
 
 """Parser for the git index file format."""
 
+import os
+import stat
 import struct
 
-from dulwich.objects import sha_to_hex, hex_to_sha
+from dulwich.objects import (
+    Tree,
+    hex_to_sha,
+    sha_to_hex,
+    )
+from dulwich.pack import (
+    SHA1Reader,
+    SHA1Writer,
+    )
 
 
 def read_cache_time(f):
+    """Read a cache time."""
     return struct.unpack(">LL", f.read(8))
 
 
 def write_cache_time(f, t):
+    """Write a cache time."""
     if isinstance(t, int):
         t = (t, 0)
+    elif isinstance(t, float):
+        (secs, nsecs) = divmod(t, 1.0)
+        t = (int(secs), int(nsecs * 1000000000))
+    elif not isinstance(t, tuple):
+        raise TypeError(t)
     f.write(struct.pack(">LL", *t))
 
 
@@ -37,22 +54,18 @@ def read_cache_entry(f):
     """Read an entry from a cache file.
 
     :param f: File-like object to read from
-    :return: tuple with: inode, device, mode, uid, gid, size, sha, flags
+    :return: tuple with: device, inode, mode, uid, gid, size, sha, flags
     """
     beginoffset = f.tell()
     ctime = read_cache_time(f)
     mtime = read_cache_time(f)
-    (ino, dev, mode, uid, gid, size, sha, flags, ) = \
+    (dev, ino, mode, uid, gid, size, sha, flags, ) = \
         struct.unpack(">LLLLLL20sH", f.read(20 + 4 * 6 + 2))
-    name = ""
-    char = f.read(1)
-    while char != "\0":
-        name += char
-        char = f.read(1)
+    name = f.read((flags & 0x0fff))
     # Padding:
-    real_size = ((f.tell() - beginoffset + 7) & ~7)
-    f.seek(beginoffset + real_size)
-    return (name, ctime, mtime, ino, dev, mode, uid, gid, size, 
+    real_size = ((f.tell() - beginoffset + 8) & ~7)
+    data = f.read((beginoffset + real_size) - f.tell())
+    return (name, ctime, mtime, dev, ino, mode, uid, gid, size, 
             sha_to_hex(sha), flags)
 
 
@@ -61,16 +74,16 @@ def write_cache_entry(f, entry):
 
     :param f: File object
     :param entry: Entry to write, tuple with: 
-        (name, ctime, mtime, ino, dev, mode, uid, gid, size, sha, flags)
+        (name, ctime, mtime, dev, ino, mode, uid, gid, size, sha, flags)
     """
     beginoffset = f.tell()
-    (name, ctime, mtime, ino, dev, mode, uid, gid, size, sha, flags) = entry
+    (name, ctime, mtime, dev, ino, mode, uid, gid, size, sha, flags) = entry
     write_cache_time(f, ctime)
     write_cache_time(f, mtime)
-    f.write(struct.pack(">LLLLLL20sH", ino, dev, mode, uid, gid, size, hex_to_sha(sha), flags))
+    flags = len(name) & flags
+    f.write(struct.pack(">LLLLLL20sH", dev, ino, mode, uid, gid, size, hex_to_sha(sha), flags))
     f.write(name)
-    f.write(chr(0))
-    real_size = ((f.tell() - beginoffset + 7) & ~7)
+    real_size = ((f.tell() - beginoffset + 8) & ~7)
     f.write("\0" * ((beginoffset + real_size) - f.tell()))
 
 
@@ -118,41 +131,70 @@ def write_index_dict(f, entries):
     write_index(f, entries_list)
 
 
+def cleanup_mode(mode):
+    if stat.S_ISLNK(fsmode):
+        mode = stat.S_IFLNK
+    else:
+        mode = stat.S_IFREG
+    mode |= (fsmode & 0111)
+    return mode
+
+
 class Index(object):
+    """A Git Index file."""
 
     def __init__(self, filename):
+        """Open an index file.
+        
+        :param filename: Path to the index file
+        """
         self._filename = filename
         self.clear()
         self.read()
 
     def write(self):
-        f = open(self._filename, 'w')
+        """Write current contents of index to disk."""
+        f = open(self._filename, 'wb')
         try:
+            f = SHA1Writer(f)
             write_index_dict(f, self._byname)
         finally:
             f.close()
 
     def read(self):
-        f = open(self._filename, 'r')
+        """Read current contents of index from disk."""
+        f = open(self._filename, 'rb')
         try:
+            f = SHA1Reader(f)
             for x in read_index(f):
                 self[x[0]] = tuple(x[1:])
+            f.check_sha()
         finally:
             f.close()
 
     def __len__(self):
+        """Number of entries in this index file."""
         return len(self._byname)
 
     def __getitem__(self, name):
+        """Retrieve entry by relative path."""
         return self._byname[name]
 
     def __iter__(self):
+        """Iterate over the paths in this index."""
         return iter(self._byname)
 
     def get_sha1(self, path):
+        """Return the (git object) SHA1 for the object at a path."""
         return self[path][-2]
 
+    def iterblobs(self):
+        """Iterate over path, sha, mode tuples for use with commit_tree."""
+        for path, entry in self:
+            yield path, entry[-2], cleanup_mode(entry[-6])
+
     def clear(self):
+        """Remove all contents from this index."""
         self._byname = {}
 
     def __setitem__(self, name, x):
@@ -167,3 +209,45 @@ class Index(object):
     def update(self, entries):
         for name, value in entries.iteritems():
             self[name] = value
+
+
+def commit_tree(object_store, blobs):
+    """Commit a new tree.
+
+    :param object_store: Object store to add trees to
+    :param blobs: Iterable over blob path, sha, mode entries
+    :return: SHA1 of the created tree.
+    """
+    trees = {"": {}}
+    def add_tree(path):
+        if path in trees:
+            return trees[path]
+        dirname, basename = os.path.split(path)
+        t = add_tree(dirname)
+        assert isinstance(basename, str)
+        newtree = {}
+        t[basename] = newtree
+        trees[path] = newtree
+        return newtree
+
+    for path, sha, mode in blobs:
+        tree_path, basename = os.path.split(path)
+        tree = add_tree(tree_path)
+        tree[basename] = (mode, sha)
+
+    def build_tree(path):
+        tree = Tree()
+        for basename, entry in trees[path].iteritems():
+            if type(entry) == dict:
+                mode = stat.S_IFDIR
+                sha = build_tree(os.path.join(path, basename))
+            else:
+                (mode, sha) = entry
+            tree.add(mode, basename, sha)
+        object_store.add_object(tree)
+        return tree.id
+    return build_tree("")
+
+
+def commit_index(object_store, index):
+    return commit_tree(object_store, index.blobs())