您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

782 行
25KB

  1. # Copyright (C) 2010, 2011 Sebastian Thiel (byronimo@gmail.com) and contributors
  2. #
  3. # This module is part of GitDB and is released under
  4. # the New BSD License: http://www.opensource.org/licenses/bsd-license.php
  5. """Contains basic c-functions which usually contain performance critical code
  6. Keeping this code separate from the beginning makes it easier to out-source
  7. it into c later, if required"""
  8. import zlib
  9. from gitdb.util import byte_ord
  10. decompressobj = zlib.decompressobj
  11. import mmap
  12. from itertools import islice
  13. from functools import reduce
  14. from gitdb.const import NULL_BYTE, BYTE_SPACE
  15. from gitdb.utils.encoding import force_text
  16. from gitdb.utils.compat import izip, buffer, xrange, PY3
  17. from gitdb.typ import (
  18. str_blob_type,
  19. str_commit_type,
  20. str_tree_type,
  21. str_tag_type,
  22. )
  23. from io import StringIO
  24. # INVARIANTS
  25. OFS_DELTA = 6
  26. REF_DELTA = 7
  27. delta_types = (OFS_DELTA, REF_DELTA)
  28. type_id_to_type_map = {
  29. 0: b'', # EXT 1
  30. 1: str_commit_type,
  31. 2: str_tree_type,
  32. 3: str_blob_type,
  33. 4: str_tag_type,
  34. 5: b'', # EXT 2
  35. OFS_DELTA: "OFS_DELTA", # OFFSET DELTA
  36. REF_DELTA: "REF_DELTA" # REFERENCE DELTA
  37. }
  38. type_to_type_id_map = {
  39. str_commit_type: 1,
  40. str_tree_type: 2,
  41. str_blob_type: 3,
  42. str_tag_type: 4,
  43. "OFS_DELTA": OFS_DELTA,
  44. "REF_DELTA": REF_DELTA,
  45. }
  46. # used when dealing with larger streams
  47. chunk_size = 1000 * mmap.PAGESIZE
  48. __all__ = ('is_loose_object', 'loose_object_header_info', 'msb_size', 'pack_object_header_info',
  49. 'write_object', 'loose_object_header', 'stream_copy', 'apply_delta_data',
  50. 'is_equal_canonical_sha', 'connect_deltas', 'DeltaChunkList', 'create_pack_object_header')
  51. #{ Structures
  52. def _set_delta_rbound(d, size):
  53. """Truncate the given delta to the given size
  54. :param size: size relative to our target offset, may not be 0, must be smaller or equal
  55. to our size
  56. :return: d"""
  57. d.ts = size
  58. # NOTE: data is truncated automatically when applying the delta
  59. # MUST NOT DO THIS HERE
  60. return d
  61. def _move_delta_lbound(d, bytes):
  62. """Move the delta by the given amount of bytes, reducing its size so that its
  63. right bound stays static
  64. :param bytes: amount of bytes to move, must be smaller than delta size
  65. :return: d"""
  66. if bytes == 0:
  67. return
  68. d.to += bytes
  69. d.so += bytes
  70. d.ts -= bytes
  71. if d.data is not None:
  72. d.data = d.data[bytes:]
  73. # END handle data
  74. return d
  75. def delta_duplicate(src):
  76. return DeltaChunk(src.to, src.ts, src.so, src.data)
  77. def delta_chunk_apply(dc, bbuf, write):
  78. """Apply own data to the target buffer
  79. :param bbuf: buffer providing source bytes for copy operations
  80. :param write: write method to call with data to write"""
  81. if dc.data is None:
  82. # COPY DATA FROM SOURCE
  83. write(buffer(bbuf, dc.so, dc.ts))
  84. else:
  85. # APPEND DATA
  86. # whats faster: if + 4 function calls or just a write with a slice ?
  87. # Considering data can be larger than 127 bytes now, it should be worth it
  88. if dc.ts < len(dc.data):
  89. write(dc.data[:dc.ts])
  90. else:
  91. write(dc.data)
  92. # END handle truncation
  93. # END handle chunk mode
  94. class DeltaChunk(object):
  95. """Represents a piece of a delta, it can either add new data, or copy existing
  96. one from a source buffer"""
  97. __slots__ = (
  98. 'to', # start offset in the target buffer in bytes
  99. 'ts', # size of this chunk in the target buffer in bytes
  100. 'so', # start offset in the source buffer in bytes or None
  101. 'data', # chunk of bytes to be added to the target buffer,
  102. # DeltaChunkList to use as base, or None
  103. )
  104. def __init__(self, to, ts, so, data):
  105. self.to = to
  106. self.ts = ts
  107. self.so = so
  108. self.data = data
  109. def __repr__(self):
  110. return "DeltaChunk(%i, %i, %s, %s)" % (self.to, self.ts, self.so, self.data or "")
  111. #{ Interface
  112. def rbound(self):
  113. return self.to + self.ts
  114. def has_data(self):
  115. """:return: True if the instance has data to add to the target stream"""
  116. return self.data is not None
  117. #} END interface
  118. def _closest_index(dcl, absofs):
  119. """:return: index at which the given absofs should be inserted. The index points
  120. to the DeltaChunk with a target buffer absofs that equals or is greater than
  121. absofs.
  122. **Note:** global method for performance only, it belongs to DeltaChunkList"""
  123. lo = 0
  124. hi = len(dcl)
  125. while lo < hi:
  126. mid = (lo + hi) / 2
  127. dc = dcl[mid]
  128. if dc.to > absofs:
  129. hi = mid
  130. elif dc.rbound() > absofs or dc.to == absofs:
  131. return mid
  132. else:
  133. lo = mid + 1
  134. # END handle bound
  135. # END for each delta absofs
  136. return len(dcl) - 1
  137. def delta_list_apply(dcl, bbuf, write):
  138. """Apply the chain's changes and write the final result using the passed
  139. write function.
  140. :param bbuf: base buffer containing the base of all deltas contained in this
  141. list. It will only be used if the chunk in question does not have a base
  142. chain.
  143. :param write: function taking a string of bytes to write to the output"""
  144. for dc in dcl:
  145. delta_chunk_apply(dc, bbuf, write)
  146. # END for each dc
  147. def delta_list_slice(dcl, absofs, size, ndcl):
  148. """:return: Subsection of this list at the given absolute offset, with the given
  149. size in bytes.
  150. :return: None"""
  151. cdi = _closest_index(dcl, absofs) # delta start index
  152. cd = dcl[cdi]
  153. slen = len(dcl)
  154. lappend = ndcl.append
  155. if cd.to != absofs:
  156. tcd = DeltaChunk(cd.to, cd.ts, cd.so, cd.data)
  157. _move_delta_lbound(tcd, absofs - cd.to)
  158. tcd.ts = min(tcd.ts, size)
  159. lappend(tcd)
  160. size -= tcd.ts
  161. cdi += 1
  162. # END lbound overlap handling
  163. while cdi < slen and size:
  164. # are we larger than the current block
  165. cd = dcl[cdi]
  166. if cd.ts <= size:
  167. lappend(DeltaChunk(cd.to, cd.ts, cd.so, cd.data))
  168. size -= cd.ts
  169. else:
  170. tcd = DeltaChunk(cd.to, cd.ts, cd.so, cd.data)
  171. tcd.ts = size
  172. lappend(tcd)
  173. size -= tcd.ts
  174. break
  175. # END hadle size
  176. cdi += 1
  177. # END for each chunk
  178. class DeltaChunkList(list):
  179. """List with special functionality to deal with DeltaChunks.
  180. There are two types of lists we represent. The one was created bottom-up, working
  181. towards the latest delta, the other kind was created top-down, working from the
  182. latest delta down to the earliest ancestor. This attribute is queryable
  183. after all processing with is_reversed."""
  184. __slots__ = tuple()
  185. def rbound(self):
  186. """:return: rightmost extend in bytes, absolute"""
  187. if len(self) == 0:
  188. return 0
  189. return self[-1].rbound()
  190. def lbound(self):
  191. """:return: leftmost byte at which this chunklist starts"""
  192. if len(self) == 0:
  193. return 0
  194. return self[0].to
  195. def size(self):
  196. """:return: size of bytes as measured by our delta chunks"""
  197. return self.rbound() - self.lbound()
  198. def apply(self, bbuf, write):
  199. """Only used by public clients, internally we only use the global routines
  200. for performance"""
  201. return delta_list_apply(self, bbuf, write)
  202. def compress(self):
  203. """Alter the list to reduce the amount of nodes. Currently we concatenate
  204. add-chunks
  205. :return: self"""
  206. slen = len(self)
  207. if slen < 2:
  208. return self
  209. i = 0
  210. first_data_index = None
  211. while i < slen:
  212. dc = self[i]
  213. i += 1
  214. if dc.data is None:
  215. if first_data_index is not None and i - 2 - first_data_index > 1:
  216. # if first_data_index is not None:
  217. nd = StringIO() # new data
  218. so = self[first_data_index].to # start offset in target buffer
  219. for x in xrange(first_data_index, i - 1):
  220. xdc = self[x]
  221. nd.write(xdc.data[:xdc.ts])
  222. # END collect data
  223. del(self[first_data_index:i - 1])
  224. buf = nd.getvalue()
  225. self.insert(first_data_index, DeltaChunk(so, len(buf), 0, buf))
  226. slen = len(self)
  227. i = first_data_index + 1
  228. # END concatenate data
  229. first_data_index = None
  230. continue
  231. # END skip non-data chunks
  232. if first_data_index is None:
  233. first_data_index = i - 1
  234. # END iterate list
  235. # if slen_orig != len(self):
  236. # print "INFO: Reduced delta list len to %f %% of former size" % ((float(len(self)) / slen_orig) * 100)
  237. return self
  238. def check_integrity(self, target_size=-1):
  239. """Verify the list has non-overlapping chunks only, and the total size matches
  240. target_size
  241. :param target_size: if not -1, the total size of the chain must be target_size
  242. :raise AssertionError: if the size doen't match"""
  243. if target_size > -1:
  244. assert self[-1].rbound() == target_size
  245. assert reduce(lambda x, y: x + y, (d.ts for d in self), 0) == target_size
  246. # END target size verification
  247. if len(self) < 2:
  248. return
  249. # check data
  250. for dc in self:
  251. assert dc.ts > 0
  252. if dc.has_data():
  253. assert len(dc.data) >= dc.ts
  254. # END for each dc
  255. left = islice(self, 0, len(self) - 1)
  256. right = iter(self)
  257. right.next()
  258. # this is very pythonic - we might have just use index based access here,
  259. # but this could actually be faster
  260. for lft, rgt in izip(left, right):
  261. assert lft.rbound() == rgt.to
  262. assert lft.to + lft.ts == rgt.to
  263. # END for each pair
  264. class TopdownDeltaChunkList(DeltaChunkList):
  265. """Represents a list which is generated by feeding its ancestor streams one by
  266. one"""
  267. __slots__ = tuple()
  268. def connect_with_next_base(self, bdcl):
  269. """Connect this chain with the next level of our base delta chunklist.
  270. The goal in this game is to mark as many of our chunks rigid, hence they
  271. cannot be changed by any of the upcoming bases anymore. Once all our
  272. chunks are marked like that, we can stop all processing
  273. :param bdcl: data chunk list being one of our bases. They must be fed in
  274. consequtively and in order, towards the earliest ancestor delta
  275. :return: True if processing was done. Use it to abort processing of
  276. remaining streams if False is returned"""
  277. nfc = 0 # number of frozen chunks
  278. dci = 0 # delta chunk index
  279. slen = len(self) # len of self
  280. ccl = list() # temporary list
  281. while dci < slen:
  282. dc = self[dci]
  283. dci += 1
  284. # all add-chunks which are already topmost don't need additional processing
  285. if dc.data is not None:
  286. nfc += 1
  287. continue
  288. # END skip add chunks
  289. # copy chunks
  290. # integrate the portion of the base list into ourselves. Lists
  291. # dont support efficient insertion ( just one at a time ), but for now
  292. # we live with it. Internally, its all just a 32/64bit pointer, and
  293. # the portions of moved memory should be smallish. Maybe we just rebuild
  294. # ourselves in order to reduce the amount of insertions ...
  295. del(ccl[:])
  296. delta_list_slice(bdcl, dc.so, dc.ts, ccl)
  297. # move the target bounds into place to match with our chunk
  298. ofs = dc.to - dc.so
  299. for cdc in ccl:
  300. cdc.to += ofs
  301. # END update target bounds
  302. if len(ccl) == 1:
  303. self[dci - 1] = ccl[0]
  304. else:
  305. # maybe try to compute the expenses here, and pick the right algorithm
  306. # It would normally be faster than copying everything physically though
  307. # TODO: Use a deque here, and decide by the index whether to extend
  308. # or extend left !
  309. post_dci = self[dci:]
  310. del(self[dci - 1:]) # include deletion of dc
  311. self.extend(ccl)
  312. self.extend(post_dci)
  313. slen = len(self)
  314. dci += len(ccl) - 1 # deleted dc, added rest
  315. # END handle chunk replacement
  316. # END for each chunk
  317. if nfc == slen:
  318. return False
  319. # END handle completeness
  320. return True
  321. #} END structures
  322. #{ Routines
  323. def is_loose_object(m):
  324. """
  325. :return: True the file contained in memory map m appears to be a loose object.
  326. Only the first two bytes are needed"""
  327. b0, b1 = map(ord, m[:2])
  328. word = (b0 << 8) + b1
  329. return b0 == 0x78 and (word % 31) == 0
  330. def loose_object_header_info(m):
  331. """
  332. :return: tuple(type_string, uncompressed_size_in_bytes) the type string of the
  333. object as well as its uncompressed size in bytes.
  334. :param m: memory map from which to read the compressed object data"""
  335. decompress_size = 8192 # is used in cgit as well
  336. hdr = decompressobj().decompress(m, decompress_size)
  337. type_name, size = hdr[:hdr.find(NULL_BYTE)].split(BYTE_SPACE)
  338. return type_name, int(size)
  339. def pack_object_header_info(data):
  340. """
  341. :return: tuple(type_id, uncompressed_size_in_bytes, byte_offset)
  342. The type_id should be interpreted according to the ``type_id_to_type_map`` map
  343. The byte-offset specifies the start of the actual zlib compressed datastream
  344. :param m: random-access memory, like a string or memory map"""
  345. c = byte_ord(data[0]) # first byte
  346. i = 1 # next char to read
  347. type_id = (c >> 4) & 7 # numeric type
  348. size = c & 15 # starting size
  349. s = 4 # starting bit-shift size
  350. if PY3:
  351. while c & 0x80:
  352. c = byte_ord(data[i])
  353. i += 1
  354. size += (c & 0x7f) << s
  355. s += 7
  356. # END character loop
  357. else:
  358. while c & 0x80:
  359. c = ord(data[i])
  360. i += 1
  361. size += (c & 0x7f) << s
  362. s += 7
  363. # END character loop
  364. # end performance at expense of maintenance ...
  365. return (type_id, size, i)
  366. def create_pack_object_header(obj_type, obj_size):
  367. """
  368. :return: string defining the pack header comprised of the object type
  369. and its incompressed size in bytes
  370. :param obj_type: pack type_id of the object
  371. :param obj_size: uncompressed size in bytes of the following object stream"""
  372. c = 0 # 1 byte
  373. if PY3:
  374. hdr = bytearray() # output string
  375. c = (obj_type << 4) | (obj_size & 0xf)
  376. obj_size >>= 4
  377. while obj_size:
  378. hdr.append(c | 0x80)
  379. c = obj_size & 0x7f
  380. obj_size >>= 7
  381. # END until size is consumed
  382. hdr.append(c)
  383. else:
  384. hdr = bytes() # output string
  385. c = (obj_type << 4) | (obj_size & 0xf)
  386. obj_size >>= 4
  387. while obj_size:
  388. hdr += chr(c | 0x80)
  389. c = obj_size & 0x7f
  390. obj_size >>= 7
  391. # END until size is consumed
  392. hdr += chr(c)
  393. # end handle interpreter
  394. return hdr
  395. def msb_size(data, offset=0):
  396. """
  397. :return: tuple(read_bytes, size) read the msb size from the given random
  398. access data starting at the given byte offset"""
  399. size = 0
  400. i = 0
  401. l = len(data)
  402. hit_msb = False
  403. if PY3:
  404. while i < l:
  405. c = data[i + offset]
  406. size |= (c & 0x7f) << i * 7
  407. i += 1
  408. if not c & 0x80:
  409. hit_msb = True
  410. break
  411. # END check msb bit
  412. # END while in range
  413. else:
  414. while i < l:
  415. c = ord(data[i + offset])
  416. size |= (c & 0x7f) << i * 7
  417. i += 1
  418. if not c & 0x80:
  419. hit_msb = True
  420. break
  421. # END check msb bit
  422. # END while in range
  423. # end performance ...
  424. if not hit_msb:
  425. raise AssertionError("Could not find terminating MSB byte in data stream")
  426. return i + offset, size
  427. def loose_object_header(type, size):
  428. """
  429. :return: bytes representing the loose object header, which is immediately
  430. followed by the content stream of size 'size'"""
  431. return ('%s %i\0' % (force_text(type), size)).encode('ascii')
  432. def write_object(type, size, read, write, chunk_size=chunk_size):
  433. """
  434. Write the object as identified by type, size and source_stream into the
  435. target_stream
  436. :param type: type string of the object
  437. :param size: amount of bytes to write from source_stream
  438. :param read: read method of a stream providing the content data
  439. :param write: write method of the output stream
  440. :param close_target_stream: if True, the target stream will be closed when
  441. the routine exits, even if an error is thrown
  442. :return: The actual amount of bytes written to stream, which includes the header and a trailing newline"""
  443. tbw = 0 # total num bytes written
  444. # WRITE HEADER: type SP size NULL
  445. tbw += write(loose_object_header(type, size))
  446. tbw += stream_copy(read, write, size, chunk_size)
  447. return tbw
  448. def stream_copy(read, write, size, chunk_size):
  449. """
  450. Copy a stream up to size bytes using the provided read and write methods,
  451. in chunks of chunk_size
  452. **Note:** its much like stream_copy utility, but operates just using methods"""
  453. dbw = 0 # num data bytes written
  454. # WRITE ALL DATA UP TO SIZE
  455. while True:
  456. cs = min(chunk_size, size - dbw)
  457. # NOTE: not all write methods return the amount of written bytes, like
  458. # mmap.write. Its bad, but we just deal with it ... perhaps its not
  459. # even less efficient
  460. # data_len = write(read(cs))
  461. # dbw += data_len
  462. data = read(cs)
  463. data_len = len(data)
  464. dbw += data_len
  465. write(data)
  466. if data_len < cs or dbw == size:
  467. break
  468. # END check for stream end
  469. # END duplicate data
  470. return dbw
  471. def connect_deltas(dstreams):
  472. """
  473. Read the condensed delta chunk information from dstream and merge its information
  474. into a list of existing delta chunks
  475. :param dstreams: iterable of delta stream objects, the delta to be applied last
  476. comes first, then all its ancestors in order
  477. :return: DeltaChunkList, containing all operations to apply"""
  478. tdcl = None # topmost dcl
  479. dcl = tdcl = TopdownDeltaChunkList()
  480. for dsi, ds in enumerate(dstreams):
  481. # print "Stream", dsi
  482. db = ds.read()
  483. delta_buf_size = ds.size
  484. # read header
  485. i, base_size = msb_size(db)
  486. i, target_size = msb_size(db, i)
  487. # interpret opcodes
  488. tbw = 0 # amount of target bytes written
  489. while i < delta_buf_size:
  490. c = ord(db[i])
  491. i += 1
  492. if c & 0x80:
  493. cp_off, cp_size = 0, 0
  494. if (c & 0x01):
  495. cp_off = ord(db[i])
  496. i += 1
  497. if (c & 0x02):
  498. cp_off |= (ord(db[i]) << 8)
  499. i += 1
  500. if (c & 0x04):
  501. cp_off |= (ord(db[i]) << 16)
  502. i += 1
  503. if (c & 0x08):
  504. cp_off |= (ord(db[i]) << 24)
  505. i += 1
  506. if (c & 0x10):
  507. cp_size = ord(db[i])
  508. i += 1
  509. if (c & 0x20):
  510. cp_size |= (ord(db[i]) << 8)
  511. i += 1
  512. if (c & 0x40):
  513. cp_size |= (ord(db[i]) << 16)
  514. i += 1
  515. if not cp_size:
  516. cp_size = 0x10000
  517. rbound = cp_off + cp_size
  518. if (rbound < cp_size or
  519. rbound > base_size):
  520. break
  521. dcl.append(DeltaChunk(tbw, cp_size, cp_off, None))
  522. tbw += cp_size
  523. elif c:
  524. # NOTE: in C, the data chunks should probably be concatenated here.
  525. # In python, we do it as a post-process
  526. dcl.append(DeltaChunk(tbw, c, 0, db[i:i + c]))
  527. i += c
  528. tbw += c
  529. else:
  530. raise ValueError("unexpected delta opcode 0")
  531. # END handle command byte
  532. # END while processing delta data
  533. dcl.compress()
  534. # merge the lists !
  535. if dsi > 0:
  536. if not tdcl.connect_with_next_base(dcl):
  537. break
  538. # END handle merge
  539. # prepare next base
  540. dcl = DeltaChunkList()
  541. # END for each delta stream
  542. return tdcl
  543. def apply_delta_data(src_buf, src_buf_size, delta_buf, delta_buf_size, write):
  544. """
  545. Apply data from a delta buffer using a source buffer to the target file
  546. :param src_buf: random access data from which the delta was created
  547. :param src_buf_size: size of the source buffer in bytes
  548. :param delta_buf_size: size fo the delta buffer in bytes
  549. :param delta_buf: random access delta data
  550. :param write: write method taking a chunk of bytes
  551. **Note:** transcribed to python from the similar routine in patch-delta.c"""
  552. i = 0
  553. db = delta_buf
  554. if PY3:
  555. while i < delta_buf_size:
  556. c = db[i]
  557. i += 1
  558. if c & 0x80:
  559. cp_off, cp_size = 0, 0
  560. if (c & 0x01):
  561. cp_off = db[i]
  562. i += 1
  563. if (c & 0x02):
  564. cp_off |= (db[i] << 8)
  565. i += 1
  566. if (c & 0x04):
  567. cp_off |= (db[i] << 16)
  568. i += 1
  569. if (c & 0x08):
  570. cp_off |= (db[i] << 24)
  571. i += 1
  572. if (c & 0x10):
  573. cp_size = db[i]
  574. i += 1
  575. if (c & 0x20):
  576. cp_size |= (db[i] << 8)
  577. i += 1
  578. if (c & 0x40):
  579. cp_size |= (db[i] << 16)
  580. i += 1
  581. if not cp_size:
  582. cp_size = 0x10000
  583. rbound = cp_off + cp_size
  584. if (rbound < cp_size or
  585. rbound > src_buf_size):
  586. break
  587. write(buffer(src_buf, cp_off, cp_size))
  588. elif c:
  589. write(db[i:i + c])
  590. i += c
  591. else:
  592. raise ValueError("unexpected delta opcode 0")
  593. # END handle command byte
  594. # END while processing delta data
  595. else:
  596. while i < delta_buf_size:
  597. c = ord(db[i])
  598. i += 1
  599. if c & 0x80:
  600. cp_off, cp_size = 0, 0
  601. if (c & 0x01):
  602. cp_off = ord(db[i])
  603. i += 1
  604. if (c & 0x02):
  605. cp_off |= (ord(db[i]) << 8)
  606. i += 1
  607. if (c & 0x04):
  608. cp_off |= (ord(db[i]) << 16)
  609. i += 1
  610. if (c & 0x08):
  611. cp_off |= (ord(db[i]) << 24)
  612. i += 1
  613. if (c & 0x10):
  614. cp_size = ord(db[i])
  615. i += 1
  616. if (c & 0x20):
  617. cp_size |= (ord(db[i]) << 8)
  618. i += 1
  619. if (c & 0x40):
  620. cp_size |= (ord(db[i]) << 16)
  621. i += 1
  622. if not cp_size:
  623. cp_size = 0x10000
  624. rbound = cp_off + cp_size
  625. if (rbound < cp_size or
  626. rbound > src_buf_size):
  627. break
  628. write(buffer(src_buf, cp_off, cp_size))
  629. elif c:
  630. write(db[i:i + c])
  631. i += c
  632. else:
  633. raise ValueError("unexpected delta opcode 0")
  634. # END handle command byte
  635. # END while processing delta data
  636. # end save byte_ord call and prevent performance regression in py2
  637. # yes, lets use the exact same error message that git uses :)
  638. assert i == delta_buf_size, "delta replay has gone wild"
  639. def is_equal_canonical_sha(canonical_length, match, sha1):
  640. """
  641. :return: True if the given lhs and rhs 20 byte binary shas
  642. The comparison will take the canonical_length of the match sha into account,
  643. hence the comparison will only use the last 4 bytes for uneven canonical representations
  644. :param match: less than 20 byte sha
  645. :param sha1: 20 byte sha"""
  646. binary_length = canonical_length // 2
  647. if match[:binary_length] != sha1[:binary_length]:
  648. return False
  649. if canonical_length - binary_length and \
  650. (byte_ord(match[-1]) ^ byte_ord(sha1[len(match) - 1])) & 0xf0:
  651. return False
  652. # END handle uneven canonnical length
  653. return True
  654. #} END routines
  655. try:
  656. from gitdb_speedups._perf import connect_deltas
  657. except ImportError:
  658. pass