1 # index.py -- File parser/write for the git index file
2 # Copyright (C) 2008-2009 Jelmer Vernooij <jelmer@samba.org>
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; version 2
7 # of the License or (at your opinion) any later version of the license.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 """Parser for the git index file format."""
25 from dulwich.objects import (
30 from dulwich.pack import (
36 def read_cache_time(f):
37 """Read a cache time."""
38 return struct.unpack(">LL", f.read(8))
41 def write_cache_time(f, t):
42 """Write a cache time."""
43 if isinstance(t, int):
45 f.write(struct.pack(">LL", *t))
48 def read_cache_entry(f):
49 """Read an entry from a cache file.
51 :param f: File-like object to read from
52 :return: tuple with: device, inode, mode, uid, gid, size, sha, flags
54 beginoffset = f.tell()
55 ctime = read_cache_time(f)
56 mtime = read_cache_time(f)
57 (dev, ino, mode, uid, gid, size, sha, flags, ) = \
58 struct.unpack(">LLLLLL20sH", f.read(20 + 4 * 6 + 2))
65 real_size = ((f.tell() - beginoffset + 7) & ~7)
66 f.read((beginoffset + real_size) - f.tell())
67 return (name, ctime, mtime, dev, ino, mode, uid, gid, size,
68 sha_to_hex(sha), flags)
71 def write_cache_entry(f, entry):
72 """Write an index entry to a file.
75 :param entry: Entry to write, tuple with:
76 (name, ctime, mtime, dev, ino, mode, uid, gid, size, sha, flags)
78 beginoffset = f.tell()
79 (name, ctime, mtime, dev, ino, mode, uid, gid, size, sha, flags) = entry
80 write_cache_time(f, ctime)
81 write_cache_time(f, mtime)
82 f.write(struct.pack(">LLLLLL20sH", dev, ino, mode, uid, gid, size, hex_to_sha(sha), flags))
85 real_size = ((f.tell() - beginoffset + 7) & ~7)
86 f.write("\0" * ((beginoffset + real_size) - f.tell()))
90 """Read an index file, yielding the individual entries."""
93 raise AssertionError("Invalid index file header: %r" % header)
94 (version, num_entries) = struct.unpack(">LL", f.read(4 * 2))
95 assert version in (1, 2)
96 for i in range(num_entries):
97 yield read_cache_entry(f)
100 def read_index_dict(f):
101 """Read an index file and return it as a dictionary.
103 :param f: File object to read from
106 for x in read_index(f):
107 ret[x[0]] = tuple(x[1:])
111 def write_index(f, entries):
112 """Write an index file.
114 :param f: File-like object to write to
115 :param entries: Iterable over the entries to write
118 f.write(struct.pack(">LL", 2, len(entries)))
120 write_cache_entry(f, x)
123 def write_index_dict(f, entries):
124 """Write an index file based on the contents of a dictionary.
128 for name in sorted(entries):
129 entries_list.append((name,) + tuple(entries[name]))
130 write_index(f, entries_list)
133 def cleanup_mode(mode):
134 if stat.S_ISLNK(fsmode):
138 mode |= (fsmode & 0111)
143 """A Git Index file."""
145 def __init__(self, filename):
146 """Open an index file.
148 :param filename: Path to the index file
150 self._filename = filename
155 """Write current contents of index to disk."""
156 f = open(self._filename, 'wb')
159 write_index_dict(f, self._byname)
164 """Read current contents of index from disk."""
165 f = open(self._filename, 'rb')
168 for x in read_index(f):
169 self[x[0]] = tuple(x[1:])
175 """Number of entries in this index file."""
176 return len(self._byname)
178 def __getitem__(self, name):
179 """Retrieve entry by relative path."""
180 return self._byname[name]
183 """Iterate over the paths in this index."""
184 return iter(self._byname)
186 def get_sha1(self, path):
187 """Return the (git object) SHA1 for the object at a path."""
188 return self[path][-2]
191 """Iterate over path, sha, mode tuples for use with commit_tree."""
192 for path, entry in self:
193 yield path, entry[-2], cleanup_mode(entry[-6])
196 """Remove all contents from this index."""
199 def __setitem__(self, name, x):
200 assert isinstance(name, str)
202 # Remove the old entry if any
203 self._byname[name] = x
206 return self._byname.iteritems()
208 def update(self, entries):
209 for name, value in entries.iteritems():
213 def commit_tree(object_store, blobs):
214 """Commit a new tree.
216 :param object_store: Object store to add trees to
217 :param blobs: Iterable over blob path, sha, mode entries
218 :return: SHA1 of the created tree.
224 dirname, basename = os.path.split(path)
225 t = add_tree(dirname)
226 assert isinstance(basename, str)
228 t[basename] = newtree
229 trees[path] = newtree
232 for path, sha, mode in blobs:
233 tree_path, basename = os.path.split(path)
234 tree = add_tree(tree_path)
235 tree[basename] = (mode, sha)
237 def build_tree(path):
239 for basename, entry in trees[path].iteritems():
240 if type(entry) == dict:
242 sha = build_tree(os.path.join(path, basename))
245 tree.add(mode, basename, sha)
246 object_store.add_object(tree)
248 return build_tree("")
251 def commit_index(object_store, index):
252 return commit_tree(object_store, index.blobs())