index.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  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. Tree,
  23. hex_to_sha,
  24. sha_to_hex,
  25. )
  26. def read_cache_time(f):
  27. """Read a cache time."""
  28. return struct.unpack(">LL", f.read(8))
  29. def write_cache_time(f, t):
  30. """Write a cache time."""
  31. if isinstance(t, int):
  32. t = (t, 0)
  33. f.write(struct.pack(">LL", *t))
  34. def read_cache_entry(f):
  35. """Read an entry from a cache file.
  36. :param f: File-like object to read from
  37. :return: tuple with: inode, device, mode, uid, gid, size, sha, flags
  38. """
  39. beginoffset = f.tell()
  40. ctime = read_cache_time(f)
  41. mtime = read_cache_time(f)
  42. (ino, dev, mode, uid, gid, size, sha, flags, ) = \
  43. struct.unpack(">LLLLLL20sH", f.read(20 + 4 * 6 + 2))
  44. name = ""
  45. char = f.read(1)
  46. while char != "\0":
  47. name += char
  48. char = f.read(1)
  49. # Padding:
  50. real_size = ((f.tell() - beginoffset + 7) & ~7)
  51. f.seek(beginoffset + real_size)
  52. return (name, ctime, mtime, ino, dev, mode, uid, gid, size,
  53. sha_to_hex(sha), flags)
  54. def write_cache_entry(f, entry):
  55. """Write an index entry to a file.
  56. :param f: File object
  57. :param entry: Entry to write, tuple with:
  58. (name, ctime, mtime, ino, dev, mode, uid, gid, size, sha, flags)
  59. """
  60. beginoffset = f.tell()
  61. (name, ctime, mtime, ino, dev, mode, uid, gid, size, sha, flags) = entry
  62. write_cache_time(f, ctime)
  63. write_cache_time(f, mtime)
  64. f.write(struct.pack(">LLLLLL20sH", ino, dev, mode, uid, gid, size, hex_to_sha(sha), flags))
  65. f.write(name)
  66. f.write(chr(0))
  67. real_size = ((f.tell() - beginoffset + 7) & ~7)
  68. f.write("\0" * ((beginoffset + real_size) - f.tell()))
  69. def read_index(f):
  70. """Read an index file, yielding the individual entries."""
  71. header = f.read(4)
  72. if header != "DIRC":
  73. raise AssertionError("Invalid index file header: %r" % header)
  74. (version, num_entries) = struct.unpack(">LL", f.read(4 * 2))
  75. assert version in (1, 2)
  76. for i in range(num_entries):
  77. yield read_cache_entry(f)
  78. def read_index_dict(f):
  79. """Read an index file and return it as a dictionary.
  80. :param f: File object to read from
  81. """
  82. ret = {}
  83. for x in read_index(f):
  84. ret[x[0]] = tuple(x[1:])
  85. return ret
  86. def write_index(f, entries):
  87. """Write an index file.
  88. :param f: File-like object to write to
  89. :param entries: Iterable over the entries to write
  90. """
  91. f.write("DIRC")
  92. f.write(struct.pack(">LL", 2, len(entries)))
  93. for x in entries:
  94. write_cache_entry(f, x)
  95. def write_index_dict(f, entries):
  96. """Write an index file based on the contents of a dictionary.
  97. """
  98. entries_list = []
  99. for name in sorted(entries):
  100. entries_list.append((name,) + tuple(entries[name]))
  101. write_index(f, entries_list)
  102. def cleanup_mode(mode):
  103. if stat.S_ISLNK(fsmode):
  104. mode = stat.S_IFLNK
  105. else:
  106. mode = stat.S_IFREG
  107. mode |= (fsmode & 0111)
  108. return mode
  109. class Index(object):
  110. """A Git Index file."""
  111. def __init__(self, filename):
  112. """Open an index file.
  113. :param filename: Path to the index file
  114. """
  115. self._filename = filename
  116. self.clear()
  117. self.read()
  118. def write(self):
  119. """Write current contents of index to disk."""
  120. f = open(self._filename, 'w')
  121. try:
  122. write_index_dict(f, self._byname)
  123. finally:
  124. f.close()
  125. def read(self):
  126. """Read current contents of index from disk."""
  127. f = open(self._filename, 'r')
  128. try:
  129. for x in read_index(f):
  130. self[x[0]] = tuple(x[1:])
  131. finally:
  132. f.close()
  133. def __len__(self):
  134. """Number of entries in this index file."""
  135. return len(self._byname)
  136. def __getitem__(self, name):
  137. """Retrieve entry by relative path."""
  138. return self._byname[name]
  139. def __iter__(self):
  140. """Iterate over the paths in this index."""
  141. return iter(self._byname)
  142. def get_sha1(self, path):
  143. """Return the (git object) SHA1 for the object at a path."""
  144. return self[path][-2]
  145. def iterblobs(self):
  146. """Iterate over path, sha, mode tuples for use with commit_tree."""
  147. for path, entry in self:
  148. yield path, entry[-2], cleanup_mode(entry[-6])
  149. def clear(self):
  150. """Remove all contents from this index."""
  151. self._byname = {}
  152. def __setitem__(self, name, x):
  153. assert isinstance(name, str)
  154. assert len(x) == 10
  155. # Remove the old entry if any
  156. self._byname[name] = x
  157. def iteritems(self):
  158. return self._byname.iteritems()
  159. def update(self, entries):
  160. for name, value in entries.iteritems():
  161. self[name] = value
  162. def commit_tree(object_store, blobs):
  163. """Commit a new tree.
  164. :param object_store: Object store to add trees to
  165. :param blobs: Iterable over blob path, sha, mode entries
  166. :return: SHA1 of the created tree.
  167. """
  168. trees = {"": {}}
  169. def add_tree(path):
  170. if path in trees:
  171. return trees[path]
  172. dirname, basename = os.path.split(path)
  173. t = add_tree(dirname)
  174. assert isinstance(basename, str)
  175. newtree = {}
  176. t[basename] = newtree
  177. return newtree
  178. for path, sha, mode in blobs:
  179. tree_path, basename = os.path.split(path)
  180. tree = add_tree(tree_path)
  181. tree[basename] = (mode, sha)
  182. for path in sorted(trees.keys(), reverse=True):
  183. tree = Tree()
  184. for basename, (mode, sha) in trees[path].iteritems():
  185. tree.add(mode, basename, sha)
  186. object_store.add_object(tree)
  187. if path != "":
  188. # Add to object store
  189. parent_path, basename = os.path.split(path)
  190. # Update sha in parent
  191. trees[parent_path][basename] = (stat.S_IFDIR, tree.id)
  192. else:
  193. return tree.id
  194. def commit_index(object_store, index):
  195. return commit_tree(object_store, index.blobs())