# 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))
"""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)
: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()))
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):
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())