index.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. # index.py -- File parser/write for the git index file
  2. # Copyright (C) 2008-2009 Jelmer Vernooij <jelmer@samba.org>
  3. # This program is free software; you can redistribute it and/or
  4. # modify it under the terms of the GNU General Public License
  5. # as published by the Free Software Foundation; version 2
  6. # of the License or (at your opinion) any later version of the license.
  7. #
  8. # This program is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with this program; if not, write to the Free Software
  15. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  16. # MA 02110-1301, USA.
  17. """Parser for the git index file format."""
  18. import os
  19. import stat
  20. import struct
  21. from dulwich.objects import (
  22. S_IFGITLINK,
  23. S_ISGITLINK,
  24. Tree,
  25. hex_to_sha,
  26. sha_to_hex,
  27. )
  28. from dulwich.pack import (
  29. SHA1Reader,
  30. SHA1Writer,
  31. )
  32. def read_cache_time(f):
  33. """Read a cache time."""
  34. return struct.unpack(">LL", f.read(8))
  35. def write_cache_time(f, t):
  36. """Write a cache time."""
  37. if isinstance(t, int):
  38. t = (t, 0)
  39. elif isinstance(t, float):
  40. (secs, nsecs) = divmod(t, 1.0)
  41. t = (int(secs), int(nsecs * 1000000000))
  42. elif not isinstance(t, tuple):
  43. raise TypeError(t)
  44. f.write(struct.pack(">LL", *t))
  45. def read_cache_entry(f):
  46. """Read an entry from a cache file.
  47. :param f: File-like object to read from
  48. :return: tuple with: device, inode, mode, uid, gid, size, sha, flags
  49. """
  50. beginoffset = f.tell()
  51. ctime = read_cache_time(f)
  52. mtime = read_cache_time(f)
  53. (dev, ino, mode, uid, gid, size, sha, flags, ) = \
  54. struct.unpack(">LLLLLL20sH", f.read(20 + 4 * 6 + 2))
  55. name = f.read((flags & 0x0fff))
  56. # Padding:
  57. real_size = ((f.tell() - beginoffset + 8) & ~7)
  58. data = f.read((beginoffset + real_size) - f.tell())
  59. return (name, ctime, mtime, dev, ino, mode, uid, gid, size,
  60. sha_to_hex(sha), flags & ~0x0fff)
  61. def write_cache_entry(f, entry):
  62. """Write an index entry to a file.
  63. :param f: File object
  64. :param entry: Entry to write, tuple with:
  65. (name, ctime, mtime, dev, ino, mode, uid, gid, size, sha, flags)
  66. """
  67. beginoffset = f.tell()
  68. (name, ctime, mtime, dev, ino, mode, uid, gid, size, sha, flags) = entry
  69. write_cache_time(f, ctime)
  70. write_cache_time(f, mtime)
  71. flags = len(name) | (flags &~ 0x0fff)
  72. f.write(struct.pack(">LLLLLL20sH", dev, ino, mode, uid, gid, size, hex_to_sha(sha), flags))
  73. f.write(name)
  74. real_size = ((f.tell() - beginoffset + 8) & ~7)
  75. f.write("\0" * ((beginoffset + real_size) - f.tell()))
  76. def read_index(f):
  77. """Read an index file, yielding the individual entries."""
  78. header = f.read(4)
  79. if header != "DIRC":
  80. raise AssertionError("Invalid index file header: %r" % header)
  81. (version, num_entries) = struct.unpack(">LL", f.read(4 * 2))
  82. assert version in (1, 2)
  83. for i in range(num_entries):
  84. yield read_cache_entry(f)
  85. def read_index_dict(f):
  86. """Read an index file and return it as a dictionary.
  87. :param f: File object to read from
  88. """
  89. ret = {}
  90. for x in read_index(f):
  91. ret[x[0]] = tuple(x[1:])
  92. return ret
  93. def write_index(f, entries):
  94. """Write an index file.
  95. :param f: File-like object to write to
  96. :param entries: Iterable over the entries to write
  97. """
  98. f.write("DIRC")
  99. f.write(struct.pack(">LL", 2, len(entries)))
  100. for x in entries:
  101. write_cache_entry(f, x)
  102. def write_index_dict(f, entries):
  103. """Write an index file based on the contents of a dictionary.
  104. """
  105. entries_list = []
  106. for name in sorted(entries):
  107. entries_list.append((name,) + tuple(entries[name]))
  108. write_index(f, entries_list)
  109. def cleanup_mode(mode):
  110. """Cleanup a mode value.
  111. """
  112. if stat.S_ISLNK(mode):
  113. return stat.S_IFLNK
  114. elif stat.S_ISDIR(mode):
  115. return stat.S_IFDIR
  116. elif S_ISGITLINK(mode):
  117. return S_IFGITLINK
  118. ret = stat.S_IFREG | 0644
  119. ret |= (mode & 0111)
  120. return ret
  121. class Index(object):
  122. """A Git Index file."""
  123. def __init__(self, filename):
  124. """Open an index file.
  125. :param filename: Path to the index file
  126. """
  127. self._filename = filename
  128. self.clear()
  129. self.read()
  130. def write(self):
  131. """Write current contents of index to disk."""
  132. f = open(self._filename, 'wb')
  133. try:
  134. f = SHA1Writer(f)
  135. write_index_dict(f, self._byname)
  136. finally:
  137. f.close()
  138. def read(self):
  139. """Read current contents of index from disk."""
  140. f = open(self._filename, 'rb')
  141. try:
  142. f = SHA1Reader(f)
  143. for x in read_index(f):
  144. self[x[0]] = tuple(x[1:])
  145. f.check_sha()
  146. finally:
  147. f.close()
  148. def __len__(self):
  149. """Number of entries in this index file."""
  150. return len(self._byname)
  151. def __getitem__(self, name):
  152. """Retrieve entry by relative path."""
  153. return self._byname[name]
  154. def __iter__(self):
  155. """Iterate over the paths in this index."""
  156. return iter(self._byname)
  157. def get_sha1(self, path):
  158. """Return the (git object) SHA1 for the object at a path."""
  159. return self[path][-2]
  160. def iterblobs(self):
  161. """Iterate over path, sha, mode tuples for use with commit_tree."""
  162. for path, entry in self:
  163. yield path, entry[-2], cleanup_mode(entry[-6])
  164. def clear(self):
  165. """Remove all contents from this index."""
  166. self._byname = {}
  167. def __setitem__(self, name, x):
  168. assert isinstance(name, str)
  169. assert len(x) == 10
  170. # Remove the old entry if any
  171. self._byname[name] = x
  172. def iteritems(self):
  173. return self._byname.iteritems()
  174. def update(self, entries):
  175. for name, value in entries.iteritems():
  176. self[name] = value
  177. def commit_tree(object_store, blobs):
  178. """Commit a new tree.
  179. :param object_store: Object store to add trees to
  180. :param blobs: Iterable over blob path, sha, mode entries
  181. :return: SHA1 of the created tree.
  182. """
  183. trees = {"": {}}
  184. def add_tree(path):
  185. if path in trees:
  186. return trees[path]
  187. dirname, basename = os.path.split(path)
  188. t = add_tree(dirname)
  189. assert isinstance(basename, str)
  190. newtree = {}
  191. t[basename] = newtree
  192. trees[path] = newtree
  193. return newtree
  194. for path, sha, mode in blobs:
  195. tree_path, basename = os.path.split(path)
  196. tree = add_tree(tree_path)
  197. tree[basename] = (mode, sha)
  198. def build_tree(path):
  199. tree = Tree()
  200. for basename, entry in trees[path].iteritems():
  201. if type(entry) == dict:
  202. mode = stat.S_IFDIR
  203. sha = build_tree(os.path.join(path, basename))
  204. else:
  205. (mode, sha) = entry
  206. tree.add(mode, basename, sha)
  207. object_store.add_object(tree)
  208. return tree.id
  209. return build_tree("")
  210. def commit_index(object_store, index):
  211. """Create a new tree from an index.
  212. :param object_store: Object store to save the tree in
  213. :param index: Index file
  214. """
  215. return commit_tree(object_store, index.iterblobs())