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 (
32 from dulwich.pack import (
38 def read_cache_time(f):
41 :param f: File-like object to read from
42 :return: Tuple with seconds and nanoseconds
44 return struct.unpack(">LL", f.read(8))
47 def write_cache_time(f, t):
48 """Write a cache time.
50 :param f: File-like object to write to
51 :param t: Time to write (as int, float or tuple with secs and nsecs)
53 if isinstance(t, int):
55 elif isinstance(t, float):
56 (secs, nsecs) = divmod(t, 1.0)
57 t = (int(secs), int(nsecs * 1000000000))
58 elif not isinstance(t, tuple):
60 f.write(struct.pack(">LL", *t))
63 def read_cache_entry(f):
64 """Read an entry from a cache file.
66 :param f: File-like object to read from
67 :return: tuple with: device, inode, mode, uid, gid, size, sha, flags
69 beginoffset = f.tell()
70 ctime = read_cache_time(f)
71 mtime = read_cache_time(f)
72 (dev, ino, mode, uid, gid, size, sha, flags, ) = \
73 struct.unpack(">LLLLLL20sH", f.read(20 + 4 * 6 + 2))
74 name = f.read((flags & 0x0fff))
76 real_size = ((f.tell() - beginoffset + 8) & ~7)
77 data = f.read((beginoffset + real_size) - f.tell())
78 return (name, ctime, mtime, dev, ino, mode, uid, gid, size,
79 sha_to_hex(sha), flags & ~0x0fff)
82 def write_cache_entry(f, entry):
83 """Write an index entry to a file.
86 :param entry: Entry to write, tuple with:
87 (name, ctime, mtime, dev, ino, mode, uid, gid, size, sha, flags)
89 beginoffset = f.tell()
90 (name, ctime, mtime, dev, ino, mode, uid, gid, size, sha, flags) = entry
91 write_cache_time(f, ctime)
92 write_cache_time(f, mtime)
93 flags = len(name) | (flags &~ 0x0fff)
94 f.write(struct.pack(">LLLLLL20sH", dev, ino, mode, uid, gid, size, hex_to_sha(sha), flags))
96 real_size = ((f.tell() - beginoffset + 8) & ~7)
97 f.write("\0" * ((beginoffset + real_size) - f.tell()))
101 """Read an index file, yielding the individual entries."""
104 raise AssertionError("Invalid index file header: %r" % header)
105 (version, num_entries) = struct.unpack(">LL", f.read(4 * 2))
106 assert version in (1, 2)
107 for i in range(num_entries):
108 yield read_cache_entry(f)
111 def read_index_dict(f):
112 """Read an index file and return it as a dictionary.
114 :param f: File object to read from
117 for x in read_index(f):
118 ret[x[0]] = tuple(x[1:])
122 def write_index(f, entries):
123 """Write an index file.
125 :param f: File-like object to write to
126 :param entries: Iterable over the entries to write
129 f.write(struct.pack(">LL", 2, len(entries)))
131 write_cache_entry(f, x)
134 def write_index_dict(f, entries):
135 """Write an index file based on the contents of a dictionary.
139 for name in sorted(entries):
140 entries_list.append((name,) + tuple(entries[name]))
141 write_index(f, entries_list)
144 def cleanup_mode(mode):
145 """Cleanup a mode value.
147 This will return a mode that can be stored in a tree object.
149 :param mode: Mode to clean up.
151 if stat.S_ISLNK(mode):
153 elif stat.S_ISDIR(mode):
155 elif S_ISGITLINK(mode):
157 ret = stat.S_IFREG | 0644
163 """A Git Index file."""
165 def __init__(self, filename):
166 """Open an index file.
168 :param filename: Path to the index file
170 self._filename = filename
175 """Write current contents of index to disk."""
176 f = open(self._filename, 'wb')
179 write_index_dict(f, self._byname)
184 """Read current contents of index from disk."""
185 f = open(self._filename, 'rb')
188 for x in read_index(f):
189 self[x[0]] = tuple(x[1:])
190 # FIXME: Additional data?
191 f.read(os.path.getsize(self._filename)-f.tell()-20)
197 """Number of entries in this index file."""
198 return len(self._byname)
200 def __getitem__(self, name):
201 """Retrieve entry by relative path.
203 :return: tuple with (ctime, mtime, dev, ino, mode, uid, gid, size, sha, flags)
205 return self._byname[name]
208 """Iterate over the paths in this index."""
209 return iter(self._byname)
211 def get_sha1(self, path):
212 """Return the (git object) SHA1 for the object at a path."""
213 return self[path][-2]
216 """Iterate over path, sha, mode tuples for use with commit_tree."""
217 for path, entry in self:
218 yield path, entry[-2], cleanup_mode(entry[-6])
221 """Remove all contents from this index."""
224 def __setitem__(self, name, x):
225 assert isinstance(name, str)
227 # Remove the old entry if any
228 self._byname[name] = x
231 return self._byname.iteritems()
233 def update(self, entries):
234 for name, value in entries.iteritems():
238 def commit_tree(object_store, blobs):
239 """Commit a new tree.
241 :param object_store: Object store to add trees to
242 :param blobs: Iterable over blob path, sha, mode entries
243 :return: SHA1 of the created tree.
249 dirname, basename = os.path.split(path)
250 t = add_tree(dirname)
251 assert isinstance(basename, str)
253 t[basename] = newtree
254 trees[path] = newtree
257 for path, sha, mode in blobs:
258 tree_path, basename = os.path.split(path)
259 tree = add_tree(tree_path)
260 tree[basename] = (mode, sha)
262 def build_tree(path):
264 for basename, entry in trees[path].iteritems():
265 if type(entry) == dict:
267 sha = build_tree(os.path.join(path, basename))
270 tree.add(mode, basename, sha)
271 object_store.add_object(tree)
273 return build_tree("")
276 def commit_index(object_store, index):
277 """Create a new tree from an index.
279 :param object_store: Object store to save the tree in
280 :param index: Index file
282 return commit_tree(object_store, index.iterblobs())