You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

385 lines
14KB

  1. # Contains standalone functions to accompany the index implementation and make it
  2. # more versatile
  3. # NOTE: Autodoc hates it if this is a docstring
  4. from io import BytesIO
  5. import os
  6. from stat import (
  7. S_IFDIR,
  8. S_IFLNK,
  9. S_ISLNK,
  10. S_ISDIR,
  11. S_IFMT,
  12. S_IFREG,
  13. )
  14. import subprocess
  15. from git.cmd import PROC_CREATIONFLAGS, handle_process_output
  16. from git.compat import (
  17. PY3,
  18. defenc,
  19. force_text,
  20. force_bytes,
  21. is_posix,
  22. safe_encode,
  23. safe_decode,
  24. )
  25. from git.exc import (
  26. UnmergedEntriesError,
  27. HookExecutionError
  28. )
  29. from git.objects.fun import (
  30. tree_to_stream,
  31. traverse_tree_recursive,
  32. traverse_trees_recursive
  33. )
  34. from git.util import IndexFileSHA1Writer, finalize_process
  35. from gitdb.base import IStream
  36. from gitdb.typ import str_tree_type
  37. import os.path as osp
  38. from .typ import (
  39. BaseIndexEntry,
  40. IndexEntry,
  41. CE_NAMEMASK,
  42. CE_STAGESHIFT
  43. )
  44. from .util import (
  45. pack,
  46. unpack
  47. )
  48. S_IFGITLINK = S_IFLNK | S_IFDIR # a submodule
  49. CE_NAMEMASK_INV = ~CE_NAMEMASK
  50. __all__ = ('write_cache', 'read_cache', 'write_tree_from_cache', 'entry_key',
  51. 'stat_mode_to_index_mode', 'S_IFGITLINK', 'run_commit_hook', 'hook_path')
  52. def hook_path(name, git_dir):
  53. """:return: path to the given named hook in the given git repository directory"""
  54. return osp.join(git_dir, 'hooks', name)
  55. def run_commit_hook(name, index, *args):
  56. """Run the commit hook of the given name. Silently ignores hooks that do not exist.
  57. :param name: name of hook, like 'pre-commit'
  58. :param index: IndexFile instance
  59. :param args: arguments passed to hook file
  60. :raises HookExecutionError: """
  61. hp = hook_path(name, index.repo.git_dir)
  62. if not os.access(hp, os.X_OK):
  63. return
  64. env = os.environ.copy()
  65. env['GIT_INDEX_FILE'] = safe_decode(index.path) if PY3 else safe_encode(index.path)
  66. env['GIT_EDITOR'] = ':'
  67. try:
  68. cmd = subprocess.Popen([hp] + list(args),
  69. env=env,
  70. stdout=subprocess.PIPE,
  71. stderr=subprocess.PIPE,
  72. cwd=index.repo.working_dir,
  73. close_fds=is_posix,
  74. creationflags=PROC_CREATIONFLAGS,)
  75. except Exception as ex:
  76. raise HookExecutionError(hp, ex)
  77. else:
  78. stdout = []
  79. stderr = []
  80. handle_process_output(cmd, stdout.append, stderr.append, finalize_process)
  81. stdout = ''.join(stdout)
  82. stderr = ''.join(stderr)
  83. if cmd.returncode != 0:
  84. stdout = force_text(stdout, defenc)
  85. stderr = force_text(stderr, defenc)
  86. raise HookExecutionError(hp, cmd.returncode, stderr, stdout)
  87. # end handle return code
  88. def stat_mode_to_index_mode(mode):
  89. """Convert the given mode from a stat call to the corresponding index mode
  90. and return it"""
  91. if S_ISLNK(mode): # symlinks
  92. return S_IFLNK
  93. if S_ISDIR(mode) or S_IFMT(mode) == S_IFGITLINK: # submodules
  94. return S_IFGITLINK
  95. return S_IFREG | 0o644 | (mode & 0o111) # blobs with or without executable bit
  96. def write_cache(entries, stream, extension_data=None, ShaStreamCls=IndexFileSHA1Writer):
  97. """Write the cache represented by entries to a stream
  98. :param entries: **sorted** list of entries
  99. :param stream: stream to wrap into the AdapterStreamCls - it is used for
  100. final output.
  101. :param ShaStreamCls: Type to use when writing to the stream. It produces a sha
  102. while writing to it, before the data is passed on to the wrapped stream
  103. :param extension_data: any kind of data to write as a trailer, it must begin
  104. a 4 byte identifier, followed by its size ( 4 bytes )"""
  105. # wrap the stream into a compatible writer
  106. stream = ShaStreamCls(stream)
  107. tell = stream.tell
  108. write = stream.write
  109. # header
  110. version = 2
  111. write(b"DIRC")
  112. write(pack(">LL", version, len(entries)))
  113. # body
  114. for entry in entries:
  115. beginoffset = tell()
  116. write(entry[4]) # ctime
  117. write(entry[5]) # mtime
  118. path = entry[3]
  119. path = force_bytes(path, encoding=defenc)
  120. plen = len(path) & CE_NAMEMASK # path length
  121. assert plen == len(path), "Path %s too long to fit into index" % entry[3]
  122. flags = plen | (entry[2] & CE_NAMEMASK_INV) # clear possible previous values
  123. write(pack(">LLLLLL20sH", entry[6], entry[7], entry[0],
  124. entry[8], entry[9], entry[10], entry[1], flags))
  125. write(path)
  126. real_size = ((tell() - beginoffset + 8) & ~7)
  127. write(b"\0" * ((beginoffset + real_size) - tell()))
  128. # END for each entry
  129. # write previously cached extensions data
  130. if extension_data is not None:
  131. stream.write(extension_data)
  132. # write the sha over the content
  133. stream.write_sha()
  134. def read_header(stream):
  135. """Return tuple(version_long, num_entries) from the given stream"""
  136. type_id = stream.read(4)
  137. if type_id != b"DIRC":
  138. raise AssertionError("Invalid index file header: %r" % type_id)
  139. version, num_entries = unpack(">LL", stream.read(4 * 2))
  140. # TODO: handle version 3: extended data, see read-cache.c
  141. assert version in (1, 2)
  142. return version, num_entries
  143. def entry_key(*entry):
  144. """:return: Key suitable to be used for the index.entries dictionary
  145. :param entry: One instance of type BaseIndexEntry or the path and the stage"""
  146. if len(entry) == 1:
  147. return (entry[0].path, entry[0].stage)
  148. else:
  149. return tuple(entry)
  150. # END handle entry
  151. def read_cache(stream):
  152. """Read a cache file from the given stream
  153. :return: tuple(version, entries_dict, extension_data, content_sha)
  154. * version is the integer version number
  155. * entries dict is a dictionary which maps IndexEntry instances to a path at a stage
  156. * extension_data is '' or 4 bytes of type + 4 bytes of size + size bytes
  157. * content_sha is a 20 byte sha on all cache file contents"""
  158. version, num_entries = read_header(stream)
  159. count = 0
  160. entries = {}
  161. read = stream.read
  162. tell = stream.tell
  163. while count < num_entries:
  164. beginoffset = tell()
  165. ctime = unpack(">8s", read(8))[0]
  166. mtime = unpack(">8s", read(8))[0]
  167. (dev, ino, mode, uid, gid, size, sha, flags) = \
  168. unpack(">LLLLLL20sH", read(20 + 4 * 6 + 2))
  169. path_size = flags & CE_NAMEMASK
  170. path = read(path_size).decode(defenc)
  171. real_size = ((tell() - beginoffset + 8) & ~7)
  172. read((beginoffset + real_size) - tell())
  173. entry = IndexEntry((mode, sha, flags, path, ctime, mtime, dev, ino, uid, gid, size))
  174. # entry_key would be the method to use, but we safe the effort
  175. entries[(path, entry.stage)] = entry
  176. count += 1
  177. # END for each entry
  178. # the footer contains extension data and a sha on the content so far
  179. # Keep the extension footer,and verify we have a sha in the end
  180. # Extension data format is:
  181. # 4 bytes ID
  182. # 4 bytes length of chunk
  183. # repeated 0 - N times
  184. extension_data = stream.read(~0)
  185. assert len(extension_data) > 19, "Index Footer was not at least a sha on content as it was only %i bytes in size"\
  186. % len(extension_data)
  187. content_sha = extension_data[-20:]
  188. # truncate the sha in the end as we will dynamically create it anyway
  189. extension_data = extension_data[:-20]
  190. return (version, entries, extension_data, content_sha)
  191. def write_tree_from_cache(entries, odb, sl, si=0):
  192. """Create a tree from the given sorted list of entries and put the respective
  193. trees into the given object database
  194. :param entries: **sorted** list of IndexEntries
  195. :param odb: object database to store the trees in
  196. :param si: start index at which we should start creating subtrees
  197. :param sl: slice indicating the range we should process on the entries list
  198. :return: tuple(binsha, list(tree_entry, ...)) a tuple of a sha and a list of
  199. tree entries being a tuple of hexsha, mode, name"""
  200. tree_items = []
  201. tree_items_append = tree_items.append
  202. ci = sl.start
  203. end = sl.stop
  204. while ci < end:
  205. entry = entries[ci]
  206. if entry.stage != 0:
  207. raise UnmergedEntriesError(entry)
  208. # END abort on unmerged
  209. ci += 1
  210. rbound = entry.path.find('/', si)
  211. if rbound == -1:
  212. # its not a tree
  213. tree_items_append((entry.binsha, entry.mode, entry.path[si:]))
  214. else:
  215. # find common base range
  216. base = entry.path[si:rbound]
  217. xi = ci
  218. while xi < end:
  219. oentry = entries[xi]
  220. orbound = oentry.path.find('/', si)
  221. if orbound == -1 or oentry.path[si:orbound] != base:
  222. break
  223. # END abort on base mismatch
  224. xi += 1
  225. # END find common base
  226. # enter recursion
  227. # ci - 1 as we want to count our current item as well
  228. sha, tree_entry_list = write_tree_from_cache(entries, odb, slice(ci - 1, xi), rbound + 1) # @UnusedVariable
  229. tree_items_append((sha, S_IFDIR, base))
  230. # skip ahead
  231. ci = xi
  232. # END handle bounds
  233. # END for each entry
  234. # finally create the tree
  235. sio = BytesIO()
  236. tree_to_stream(tree_items, sio.write)
  237. sio.seek(0)
  238. istream = odb.store(IStream(str_tree_type, len(sio.getvalue()), sio))
  239. return (istream.binsha, tree_items)
  240. def _tree_entry_to_baseindexentry(tree_entry, stage):
  241. return BaseIndexEntry((tree_entry[1], tree_entry[0], stage << CE_STAGESHIFT, tree_entry[2]))
  242. def aggressive_tree_merge(odb, tree_shas):
  243. """
  244. :return: list of BaseIndexEntries representing the aggressive merge of the given
  245. trees. All valid entries are on stage 0, whereas the conflicting ones are left
  246. on stage 1, 2 or 3, whereas stage 1 corresponds to the common ancestor tree,
  247. 2 to our tree and 3 to 'their' tree.
  248. :param tree_shas: 1, 2 or 3 trees as identified by their binary 20 byte shas
  249. If 1 or two, the entries will effectively correspond to the last given tree
  250. If 3 are given, a 3 way merge is performed"""
  251. out = []
  252. out_append = out.append
  253. # one and two way is the same for us, as we don't have to handle an existing
  254. # index, instrea
  255. if len(tree_shas) in (1, 2):
  256. for entry in traverse_tree_recursive(odb, tree_shas[-1], ''):
  257. out_append(_tree_entry_to_baseindexentry(entry, 0))
  258. # END for each entry
  259. return out
  260. # END handle single tree
  261. if len(tree_shas) > 3:
  262. raise ValueError("Cannot handle %i trees at once" % len(tree_shas))
  263. # three trees
  264. for base, ours, theirs in traverse_trees_recursive(odb, tree_shas, ''):
  265. if base is not None:
  266. # base version exists
  267. if ours is not None:
  268. # ours exists
  269. if theirs is not None:
  270. # it exists in all branches, if it was changed in both
  271. # its a conflict, otherwise we take the changed version
  272. # This should be the most common branch, so it comes first
  273. if(base[0] != ours[0] and base[0] != theirs[0] and ours[0] != theirs[0]) or \
  274. (base[1] != ours[1] and base[1] != theirs[1] and ours[1] != theirs[1]):
  275. # changed by both
  276. out_append(_tree_entry_to_baseindexentry(base, 1))
  277. out_append(_tree_entry_to_baseindexentry(ours, 2))
  278. out_append(_tree_entry_to_baseindexentry(theirs, 3))
  279. elif base[0] != ours[0] or base[1] != ours[1]:
  280. # only we changed it
  281. out_append(_tree_entry_to_baseindexentry(ours, 0))
  282. else:
  283. # either nobody changed it, or they did. In either
  284. # case, use theirs
  285. out_append(_tree_entry_to_baseindexentry(theirs, 0))
  286. # END handle modification
  287. else:
  288. if ours[0] != base[0] or ours[1] != base[1]:
  289. # they deleted it, we changed it, conflict
  290. out_append(_tree_entry_to_baseindexentry(base, 1))
  291. out_append(_tree_entry_to_baseindexentry(ours, 2))
  292. # else:
  293. # we didn't change it, ignore
  294. # pass
  295. # END handle our change
  296. # END handle theirs
  297. else:
  298. if theirs is None:
  299. # deleted in both, its fine - its out
  300. pass
  301. else:
  302. if theirs[0] != base[0] or theirs[1] != base[1]:
  303. # deleted in ours, changed theirs, conflict
  304. out_append(_tree_entry_to_baseindexentry(base, 1))
  305. out_append(_tree_entry_to_baseindexentry(theirs, 3))
  306. # END theirs changed
  307. # else:
  308. # theirs didn't change
  309. # pass
  310. # END handle theirs
  311. # END handle ours
  312. else:
  313. # all three can't be None
  314. if ours is None:
  315. # added in their branch
  316. out_append(_tree_entry_to_baseindexentry(theirs, 0))
  317. elif theirs is None:
  318. # added in our branch
  319. out_append(_tree_entry_to_baseindexentry(ours, 0))
  320. else:
  321. # both have it, except for the base, see whether it changed
  322. if ours[0] != theirs[0] or ours[1] != theirs[1]:
  323. out_append(_tree_entry_to_baseindexentry(ours, 2))
  324. out_append(_tree_entry_to_baseindexentry(theirs, 3))
  325. else:
  326. # it was added the same in both
  327. out_append(_tree_entry_to_baseindexentry(ours, 0))
  328. # END handle two items
  329. # END handle heads
  330. # END handle base exists
  331. # END for each entries tuple
  332. return out