1 # index.py -- File parser/writer 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.file import GitFile
26 from dulwich.objects import (
33 from dulwich.pack import (
40 """Split a /-delimited path into a directory part and a basename.
42 :param path: The path to split.
43 :return: Tuple with directory name and basename
46 (dirname, basename) = path.rsplit("/", 1)
50 return (dirname, basename)
54 """Join a /-delimited path.
57 return "/".join([p for p in args if p])
60 def read_cache_time(f):
63 :param f: File-like object to read from
64 :return: Tuple with seconds and nanoseconds
66 return struct.unpack(">LL", f.read(8))
69 def write_cache_time(f, t):
70 """Write a cache time.
72 :param f: File-like object to write to
73 :param t: Time to write (as int, float or tuple with secs and nsecs)
75 if isinstance(t, int):
77 elif isinstance(t, float):
78 (secs, nsecs) = divmod(t, 1.0)
79 t = (int(secs), int(nsecs * 1000000000))
80 elif not isinstance(t, tuple):
82 f.write(struct.pack(">LL", *t))
85 def read_cache_entry(f):
86 """Read an entry from a cache file.
88 :param f: File-like object to read from
89 :return: tuple with: device, inode, mode, uid, gid, size, sha, flags
91 beginoffset = f.tell()
92 ctime = read_cache_time(f)
93 mtime = read_cache_time(f)
94 (dev, ino, mode, uid, gid, size, sha, flags, ) = \
95 struct.unpack(">LLLLLL20sH", f.read(20 + 4 * 6 + 2))
96 name = f.read((flags & 0x0fff))
98 real_size = ((f.tell() - beginoffset + 8) & ~7)
99 data = f.read((beginoffset + real_size) - f.tell())
100 return (name, ctime, mtime, dev, ino, mode, uid, gid, size,
101 sha_to_hex(sha), flags & ~0x0fff)
104 def write_cache_entry(f, entry):
105 """Write an index entry to a file.
107 :param f: File object
108 :param entry: Entry to write, tuple with:
109 (name, ctime, mtime, dev, ino, mode, uid, gid, size, sha, flags)
111 beginoffset = f.tell()
112 (name, ctime, mtime, dev, ino, mode, uid, gid, size, sha, flags) = entry
113 write_cache_time(f, ctime)
114 write_cache_time(f, mtime)
115 flags = len(name) | (flags &~ 0x0fff)
116 f.write(struct.pack(">LLLLLL20sH", dev, ino, mode, uid, gid, size, hex_to_sha(sha), flags))
118 real_size = ((f.tell() - beginoffset + 8) & ~7)
119 f.write("\0" * ((beginoffset + real_size) - f.tell()))
123 """Read an index file, yielding the individual entries."""
126 raise AssertionError("Invalid index file header: %r" % header)
127 (version, num_entries) = struct.unpack(">LL", f.read(4 * 2))
128 assert version in (1, 2)
129 for i in range(num_entries):
130 yield read_cache_entry(f)
133 def read_index_dict(f):
134 """Read an index file and return it as a dictionary.
136 :param f: File object to read from
139 for x in read_index(f):
140 ret[x[0]] = tuple(x[1:])
144 def write_index(f, entries):
145 """Write an index file.
147 :param f: File-like object to write to
148 :param entries: Iterable over the entries to write
151 f.write(struct.pack(">LL", 2, len(entries)))
153 write_cache_entry(f, x)
156 def write_index_dict(f, entries):
157 """Write an index file based on the contents of a dictionary.
161 for name in sorted(entries):
162 entries_list.append((name,) + tuple(entries[name]))
163 write_index(f, entries_list)
166 def cleanup_mode(mode):
167 """Cleanup a mode value.
169 This will return a mode that can be stored in a tree object.
171 :param mode: Mode to clean up.
173 if stat.S_ISLNK(mode):
175 elif stat.S_ISDIR(mode):
177 elif S_ISGITLINK(mode):
179 ret = stat.S_IFREG | 0644
185 """A Git Index file."""
187 def __init__(self, filename):
188 """Open an index file.
190 :param filename: Path to the index file
192 self._filename = filename
197 """Write current contents of index to disk."""
198 f = GitFile(self._filename, 'wb')
201 write_index_dict(f, self._byname)
206 """Read current contents of index from disk."""
207 if not os.path.exists(self._filename):
209 f = GitFile(self._filename, 'rb')
212 for x in read_index(f):
213 self[x[0]] = tuple(x[1:])
214 # FIXME: Additional data?
215 f.read(os.path.getsize(self._filename)-f.tell()-20)
221 """Number of entries in this index file."""
222 return len(self._byname)
224 def __getitem__(self, name):
225 """Retrieve entry by relative path.
227 :return: tuple with (ctime, mtime, dev, ino, mode, uid, gid, size, sha, flags)
229 return self._byname[name]
232 """Iterate over the paths in this index."""
233 return iter(self._byname)
235 def get_sha1(self, path):
236 """Return the (git object) SHA1 for the object at a path."""
237 return self[path][-2]
239 def get_mode(self, path):
240 """Return the POSIX file mode for the object at a path."""
241 return self[path][-6]
244 """Iterate over path, sha, mode tuples for use with commit_tree."""
247 yield path, entry[-2], cleanup_mode(entry[-6])
250 """Remove all contents from this index."""
253 def __setitem__(self, name, x):
254 assert isinstance(name, str)
256 # Remove the old entry if any
257 self._byname[name] = x
259 def __delitem__(self, name):
260 assert isinstance(name, str)
261 del self._byname[name]
264 return self._byname.iteritems()
266 def update(self, entries):
267 for name, value in entries.iteritems():
270 def changes_from_tree(self, object_store, tree, want_unchanged=False):
271 """Find the differences between the contents of this index and a tree.
273 :param object_store: Object store to use for retrieving tree contents
274 :param tree: SHA1 of the root tree
275 :param want_unchanged: Whether unchanged files should be reported
276 :return: Iterator over tuples with (oldpath, newpath), (oldmode, newmode), (oldsha, newsha)
278 mine = set(self._byname.keys())
279 for (name, mode, sha) in object_store.iter_tree_contents(tree):
281 if (want_unchanged or self.get_sha1(name) != sha or
282 self.get_mode(name) != mode):
283 yield ((name, name), (mode, self.get_mode(name)), (sha, self.get_sha1(name)))
287 yield ((name, None), (mode, None), (sha, None))
288 # Mention added files
290 yield ((None, name), (None, self.get_mode(name)), (None, self.get_sha1(name)))
292 def commit(self, object_store):
293 """Create a new tree from an index.
295 :param object_store: Object store to save the tree in
296 :return: Root tree SHA
298 return commit_tree(object_store, self.iterblobs())
301 def commit_tree(object_store, blobs):
302 """Commit a new tree.
304 :param object_store: Object store to add trees to
305 :param blobs: Iterable over blob path, sha, mode entries
306 :return: SHA1 of the created tree.
312 dirname, basename = pathsplit(path)
313 t = add_tree(dirname)
314 assert isinstance(basename, str)
316 t[basename] = newtree
317 trees[path] = newtree
320 for path, sha, mode in blobs:
321 tree_path, basename = pathsplit(path)
322 tree = add_tree(tree_path)
323 tree[basename] = (mode, sha)
325 def build_tree(path):
327 for basename, entry in trees[path].iteritems():
328 if type(entry) == dict:
330 sha = build_tree(pathjoin(path, basename))
333 tree.add(mode, basename, sha)
334 object_store.add_object(tree)
336 return build_tree("")
339 def commit_index(object_store, index):
340 """Create a new tree from an index.
342 :param object_store: Object store to save the tree in
343 :param index: Index file
344 :note: This function is deprecated, use index.commit() instead.
345 :return: Root tree sha.
347 return commit_tree(object_store, index.iterblobs())
350 # Hold on to the pure-python implementations for testing
353 # Try to import C versions
354 from dulwich._index import Index