lib.rs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655
  1. /*
  2. * Copyright (C) 2009 Jelmer Vernooij <jelmer@jelmer.uk>
  3. *
  4. * Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
  5. * General Public License as published by the Free Software Foundation; version 2.0
  6. * or (at your option) any later version. You can redistribute it and/or
  7. * modify it under the terms of either of these two licenses.
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. *
  15. * You should have received a copy of the licenses; if not, see
  16. * <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
  17. * and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
  18. * License, Version 2.0.
  19. */
  20. // Allow PyO3 macro-generated interior mutable constants
  21. #![allow(clippy::declare_interior_mutable_const)]
  22. use pyo3::exceptions::{PyTypeError, PyValueError};
  23. use pyo3::prelude::*;
  24. use pyo3::types::{PyBytes, PyList};
  25. pyo3::import_exception!(dulwich.errors, ApplyDeltaError);
  26. fn py_is_sha(sha: &Py<PyAny>, py: Python) -> PyResult<bool> {
  27. // Check if the object is a bytes object
  28. if sha.bind(py).is_instance_of::<PyBytes>() {
  29. // Check if the bytes object has a size of 20
  30. if sha.extract::<&[u8]>(py)?.len() == 20 {
  31. Ok(true)
  32. } else {
  33. Ok(false)
  34. }
  35. } else {
  36. Ok(false)
  37. }
  38. }
  39. #[pyfunction]
  40. fn bisect_find_sha(
  41. py: Python,
  42. start: i32,
  43. end: i32,
  44. sha: Py<PyBytes>,
  45. unpack_name: Py<PyAny>,
  46. ) -> PyResult<Option<i32>> {
  47. // Convert sha_obj to a byte slice
  48. let sha = sha.as_bytes(py);
  49. let sha_len = sha.len();
  50. // Check if sha is 20 bytes long
  51. if sha_len != 20 {
  52. return Err(PyValueError::new_err("Sha is not 20 bytes long"));
  53. }
  54. // Check if start > end
  55. if start > end {
  56. return Err(PyValueError::new_err("start > end"));
  57. }
  58. // Binary search loop
  59. let mut start = start;
  60. let mut end = end;
  61. loop {
  62. if start > end {
  63. break;
  64. }
  65. let i = (start + end) / 2;
  66. let file_sha = unpack_name.call1(py, (i,))?;
  67. if !py_is_sha(&file_sha, py)? {
  68. return Err(PyTypeError::new_err("unpack_name returned non-sha object"));
  69. }
  70. match file_sha.extract::<&[u8]>(py).unwrap().cmp(sha) {
  71. std::cmp::Ordering::Less => {
  72. start = i + 1;
  73. }
  74. std::cmp::Ordering::Greater => {
  75. end = i - 1;
  76. }
  77. std::cmp::Ordering::Equal => {
  78. return Ok(Some(i));
  79. }
  80. }
  81. }
  82. Ok(None)
  83. }
  84. fn get_delta_header_size(delta: &[u8], index: &mut usize, length: usize) -> usize {
  85. let mut size: usize = 0;
  86. let mut i: usize = 0;
  87. while *index < length {
  88. let cmd = delta[*index];
  89. *index += 1;
  90. size |= ((cmd & !0x80) as usize) << i;
  91. i += 7;
  92. if cmd & 0x80 == 0 {
  93. break;
  94. }
  95. }
  96. size
  97. }
  98. fn py_chunked_as_string<'a>(
  99. py: Python<'a>,
  100. py_buf: &'a Py<PyAny>,
  101. ) -> PyResult<std::borrow::Cow<'a, [u8]>> {
  102. if let Ok(py_list) = py_buf.extract::<Bound<PyList>>(py) {
  103. let mut buf = Vec::new();
  104. for chunk in py_list.iter() {
  105. if let Ok(chunk) = chunk.extract::<&[u8]>() {
  106. buf.extend_from_slice(chunk);
  107. } else if let Ok(chunk) = chunk.extract::<Vec<u8>>() {
  108. buf.extend(chunk);
  109. } else {
  110. return Err(PyTypeError::new_err(format!(
  111. "chunk is not a byte string, but a {:?}",
  112. chunk.get_type().name()
  113. )));
  114. }
  115. }
  116. Ok(buf.into())
  117. } else if py_buf.extract::<Bound<PyBytes>>(py).is_ok() {
  118. Ok(std::borrow::Cow::Borrowed(py_buf.extract::<&[u8]>(py)?))
  119. } else {
  120. Err(PyTypeError::new_err(
  121. "buf is not a string or a list of chunks",
  122. ))
  123. }
  124. }
  125. #[pyfunction]
  126. fn apply_delta(py: Python, py_src_buf: Py<PyAny>, py_delta: Py<PyAny>) -> PyResult<Vec<Py<PyAny>>> {
  127. let src_buf = py_chunked_as_string(py, &py_src_buf)?;
  128. let delta = py_chunked_as_string(py, &py_delta)?;
  129. let src_buf_len = src_buf.len();
  130. let delta_len = delta.len();
  131. let mut index = 0;
  132. let src_size = get_delta_header_size(delta.as_ref(), &mut index, delta_len);
  133. if src_size != src_buf_len {
  134. return Err(ApplyDeltaError::new_err(format!(
  135. "Unexpected source buffer size: {} vs {}",
  136. src_size, src_buf_len
  137. )));
  138. }
  139. let dest_size = get_delta_header_size(delta.as_ref(), &mut index, delta_len);
  140. let mut out = vec![0; dest_size];
  141. let mut outindex = 0;
  142. while index < delta_len {
  143. let cmd = delta[index];
  144. index += 1;
  145. if cmd & 0x80 != 0 {
  146. let mut cp_off = 0;
  147. let mut cp_size = 0;
  148. for i in 0..4 {
  149. if cmd & (1 << i) != 0 {
  150. let x = delta[index] as usize;
  151. index += 1;
  152. cp_off |= x << (i * 8);
  153. }
  154. }
  155. for i in 0..3 {
  156. if cmd & (1 << (4 + i)) != 0 {
  157. let x = delta[index] as usize;
  158. index += 1;
  159. cp_size |= x << (i * 8);
  160. }
  161. }
  162. if cp_size == 0 {
  163. cp_size = 0x10000;
  164. }
  165. // Check for overflow and bounds
  166. if cp_size > src_size
  167. || cp_off > src_size
  168. || cp_off > src_size - cp_size
  169. || cp_size > dest_size
  170. {
  171. break;
  172. }
  173. out[outindex..outindex + cp_size].copy_from_slice(&src_buf[cp_off..cp_off + cp_size]);
  174. outindex += cp_size;
  175. } else if cmd != 0 {
  176. if (cmd as usize) > dest_size {
  177. break;
  178. }
  179. // Raise ApplyDeltaError if there are more bytes to copy than space
  180. if outindex + cmd as usize > dest_size {
  181. return Err(ApplyDeltaError::new_err("Not enough space to copy"));
  182. }
  183. out[outindex..outindex + cmd as usize]
  184. .copy_from_slice(&delta[index..index + cmd as usize]);
  185. outindex += cmd as usize;
  186. index += cmd as usize;
  187. } else {
  188. return Err(ApplyDeltaError::new_err("Invalid opcode 0"));
  189. }
  190. }
  191. if index != delta_len {
  192. return Err(ApplyDeltaError::new_err("delta not empty"));
  193. }
  194. if outindex != dest_size {
  195. return Err(ApplyDeltaError::new_err("dest size incorrect"));
  196. }
  197. Ok(vec![PyBytes::new(py, &out).into()])
  198. }
  199. /// Encode a size value for delta headers using variable-length encoding.
  200. /// This matches Python's _delta_encode_size function.
  201. fn delta_encode_size(mut size: usize) -> Vec<u8> {
  202. let mut ret = Vec::new();
  203. let mut c = (size & 0x7F) as u8;
  204. size >>= 7;
  205. while size > 0 {
  206. ret.push(c | 0x80);
  207. c = (size & 0x7F) as u8;
  208. size >>= 7;
  209. }
  210. ret.push(c);
  211. ret
  212. }
  213. /// The length of delta compression copy operations in version 2 packs is limited
  214. /// to 64K. To copy more, we use several copy operations.
  215. const MAX_COPY_LEN: usize = 0xFFFF;
  216. /// Encode a copy operation for the delta format.
  217. /// This matches Python's _encode_copy_operation function.
  218. fn encode_copy_operation(start: usize, length: usize) -> Vec<u8> {
  219. let mut scratch = vec![0x80u8];
  220. // Encode offset (4 bytes max)
  221. for i in 0..4 {
  222. if start & (0xFF << (i * 8)) != 0 {
  223. scratch.push(((start >> (i * 8)) & 0xFF) as u8);
  224. scratch[0] |= 1 << i;
  225. }
  226. }
  227. // Encode length (2 bytes for version 2 packs)
  228. for i in 0..2 {
  229. if length & (0xFF << (i * 8)) != 0 {
  230. scratch.push(((length >> (i * 8)) & 0xFF) as u8);
  231. scratch[0] |= 1 << (4 + i);
  232. }
  233. }
  234. scratch
  235. }
  236. /// Create a delta that transforms base_buf into target_buf.
  237. /// This uses the similar crate to find matching sequences, similar to
  238. /// Python's difflib.SequenceMatcher.
  239. fn create_delta_internal(base_buf: &[u8], target_buf: &[u8]) -> Vec<u8> {
  240. let mut result = Vec::new();
  241. // Write delta header
  242. result.extend(delta_encode_size(base_buf.len()));
  243. result.extend(delta_encode_size(target_buf.len()));
  244. // Use similar crate to compute the diff at byte level
  245. let ops = similar::capture_diff_slices(similar::Algorithm::Myers, base_buf, target_buf);
  246. let mut old_pos = 0;
  247. let mut new_pos = 0;
  248. for op in ops {
  249. match op {
  250. similar::DiffOp::Equal {
  251. old_index,
  252. new_index,
  253. len,
  254. } => {
  255. // Sanity check
  256. assert_eq!(old_index, old_pos);
  257. assert_eq!(new_index, new_pos);
  258. // Emit copy operations from base_buf
  259. let mut copy_start = old_index;
  260. let mut copy_len = len;
  261. while copy_len > 0 {
  262. let to_copy = copy_len.min(MAX_COPY_LEN);
  263. result.extend(encode_copy_operation(copy_start, to_copy));
  264. copy_start += to_copy;
  265. copy_len -= to_copy;
  266. }
  267. old_pos += len;
  268. new_pos += len;
  269. }
  270. similar::DiffOp::Delete {
  271. old_index, old_len, ..
  272. } => {
  273. // Git delta format doesn't care about deletes from base
  274. assert_eq!(old_index, old_pos);
  275. old_pos += old_len;
  276. }
  277. similar::DiffOp::Insert {
  278. new_index, new_len, ..
  279. } => {
  280. // Emit literal data from target_buf
  281. assert_eq!(new_index, new_pos);
  282. let data = &target_buf[new_index..new_index + new_len];
  283. let mut remaining = data.len();
  284. let mut offset = 0;
  285. while remaining > 0 {
  286. let chunk_size = remaining.min(127);
  287. result.push(chunk_size as u8);
  288. result.extend_from_slice(&data[offset..offset + chunk_size]);
  289. offset += chunk_size;
  290. remaining -= chunk_size;
  291. }
  292. new_pos += new_len;
  293. }
  294. similar::DiffOp::Replace {
  295. old_index,
  296. old_len,
  297. new_index,
  298. new_len,
  299. } => {
  300. // For replace operations, we delete from old and insert from new
  301. // Git delta format doesn't care about deletes, so just emit insert
  302. assert_eq!(old_index, old_pos);
  303. assert_eq!(new_index, new_pos);
  304. let data = &target_buf[new_index..new_index + new_len];
  305. let mut remaining = data.len();
  306. let mut offset = 0;
  307. while remaining > 0 {
  308. let chunk_size = remaining.min(127);
  309. result.push(chunk_size as u8);
  310. result.extend_from_slice(&data[offset..offset + chunk_size]);
  311. offset += chunk_size;
  312. remaining -= chunk_size;
  313. }
  314. old_pos += old_len;
  315. new_pos += new_len;
  316. }
  317. }
  318. }
  319. result
  320. }
  321. #[pyfunction]
  322. fn create_delta(
  323. py: Python,
  324. py_base_buf: Py<PyAny>,
  325. py_target_buf: Py<PyAny>,
  326. ) -> PyResult<Py<PyBytes>> {
  327. let base_buf = py_chunked_as_string(py, &py_base_buf)?;
  328. let target_buf = py_chunked_as_string(py, &py_target_buf)?;
  329. let delta = create_delta_internal(base_buf.as_ref(), target_buf.as_ref());
  330. Ok(PyBytes::new(py, &delta).into())
  331. }
  332. #[cfg(test)]
  333. mod tests {
  334. use super::*;
  335. #[test]
  336. fn test_delta_encode_size_zero() {
  337. assert_eq!(delta_encode_size(0), vec![0]);
  338. }
  339. #[test]
  340. fn test_delta_encode_size_small() {
  341. // Values that fit in 7 bits (0-127)
  342. assert_eq!(delta_encode_size(1), vec![1]);
  343. assert_eq!(delta_encode_size(127), vec![127]);
  344. }
  345. #[test]
  346. fn test_delta_encode_size_medium() {
  347. // Values that need 2 bytes (128-16383)
  348. assert_eq!(delta_encode_size(128), vec![0x80, 0x01]);
  349. assert_eq!(delta_encode_size(256), vec![0x80, 0x02]);
  350. assert_eq!(delta_encode_size(16383), vec![0xFF, 0x7F]);
  351. }
  352. #[test]
  353. fn test_delta_encode_size_large() {
  354. // Values that need 3 bytes (16384-2097151)
  355. assert_eq!(delta_encode_size(16384), vec![0x80, 0x80, 0x01]);
  356. assert_eq!(delta_encode_size(65536), vec![0x80, 0x80, 0x04]);
  357. }
  358. #[test]
  359. fn test_delta_encode_size_very_large() {
  360. // Values that need 4+ bytes
  361. assert_eq!(delta_encode_size(1048576), vec![0x80, 0x80, 0x40]); // 1MB = 2^20
  362. assert_eq!(delta_encode_size(16777216), vec![0x80, 0x80, 0x80, 0x08]); // 16MB = 2^24
  363. }
  364. #[test]
  365. fn test_get_delta_header_size_basic() {
  366. // Test decoding various encoded sizes
  367. let mut index = 0;
  368. let delta = vec![0x00];
  369. assert_eq!(get_delta_header_size(&delta, &mut index, delta.len()), 0);
  370. assert_eq!(index, 1);
  371. let mut index = 0;
  372. let delta = vec![0x01];
  373. assert_eq!(get_delta_header_size(&delta, &mut index, delta.len()), 1);
  374. assert_eq!(index, 1);
  375. let mut index = 0;
  376. let delta = vec![127];
  377. assert_eq!(get_delta_header_size(&delta, &mut index, delta.len()), 127);
  378. assert_eq!(index, 1);
  379. }
  380. #[test]
  381. fn test_get_delta_header_size_multibyte() {
  382. // Test decoding multi-byte sizes
  383. let mut index = 0;
  384. let delta = vec![0x80, 0x01];
  385. assert_eq!(get_delta_header_size(&delta, &mut index, delta.len()), 128);
  386. assert_eq!(index, 2);
  387. let mut index = 0;
  388. let delta = vec![0x80, 0x02];
  389. assert_eq!(get_delta_header_size(&delta, &mut index, delta.len()), 256);
  390. assert_eq!(index, 2);
  391. let mut index = 0;
  392. let delta = vec![0x80, 0x80, 0x01];
  393. assert_eq!(
  394. get_delta_header_size(&delta, &mut index, delta.len()),
  395. 16384
  396. );
  397. assert_eq!(index, 3);
  398. }
  399. #[test]
  400. fn test_delta_encode_decode_roundtrip() {
  401. // Test that encoding and decoding are inverse operations
  402. let test_values = vec![0, 1, 127, 128, 255, 256, 1000, 16384, 65536, 1048576];
  403. for value in test_values {
  404. let encoded = delta_encode_size(value);
  405. let mut index = 0;
  406. let decoded = get_delta_header_size(&encoded, &mut index, encoded.len());
  407. assert_eq!(
  408. decoded, value,
  409. "Roundtrip failed for value {}: encoded {:?}, decoded {}",
  410. value, encoded, decoded
  411. );
  412. assert_eq!(index, encoded.len());
  413. }
  414. }
  415. #[test]
  416. fn test_encode_copy_operation_zero_offset() {
  417. // Copy from offset 0
  418. let result = encode_copy_operation(0, 10);
  419. // Should have copy bit set
  420. assert_eq!(result[0] & 0x80, 0x80);
  421. // Should encode length 10
  422. assert_eq!(result[0] & 0x10, 0x10); // Length bit 0 set
  423. assert_eq!(result[1], 10);
  424. assert_eq!(result.len(), 2);
  425. }
  426. #[test]
  427. fn test_encode_copy_operation_small_offset() {
  428. // Copy from offset 100, length 20
  429. let result = encode_copy_operation(100, 20);
  430. assert_eq!(result[0] & 0x80, 0x80); // Copy bit
  431. assert_eq!(result[0] & 0x01, 0x01); // Offset byte 0 present
  432. assert_eq!(result[0] & 0x10, 0x10); // Length byte 0 present
  433. assert_eq!(result[1], 100); // Offset byte 0
  434. assert_eq!(result[2], 20); // Length byte 0
  435. assert_eq!(result.len(), 3);
  436. }
  437. #[test]
  438. fn test_encode_copy_operation_large_offset() {
  439. // Copy from offset 0x12345, length 0x678
  440. let result = encode_copy_operation(0x12345, 0x678);
  441. assert_eq!(result[0] & 0x80, 0x80); // Copy bit
  442. assert_eq!(result[0] & 0x07, 0x07); // Offset bytes 0,1,2 present
  443. assert_eq!(result[0] & 0x30, 0x30); // Length bytes 0,1 present
  444. assert_eq!(result[1], 0x45); // Offset byte 0
  445. assert_eq!(result[2], 0x23); // Offset byte 1
  446. assert_eq!(result[3], 0x01); // Offset byte 2
  447. assert_eq!(result[4], 0x78); // Length byte 0
  448. assert_eq!(result[5], 0x06); // Length byte 1
  449. assert_eq!(result.len(), 6);
  450. }
  451. #[test]
  452. fn test_encode_copy_operation_max_offset() {
  453. // Test maximum offset (needs 4 bytes)
  454. let max_offset = 0xFFFFFFFF;
  455. let result = encode_copy_operation(max_offset, 1);
  456. assert_eq!(result[0] & 0x80, 0x80); // Copy bit
  457. assert_eq!(result[0] & 0x0F, 0x0F); // All 4 offset bytes present
  458. assert_eq!(result[1], 0xFF); // Offset byte 0
  459. assert_eq!(result[2], 0xFF); // Offset byte 1
  460. assert_eq!(result[3], 0xFF); // Offset byte 2
  461. assert_eq!(result[4], 0xFF); // Offset byte 3
  462. assert_eq!(result.len(), 6); // 1 cmd + 4 offset + 1 length
  463. }
  464. #[test]
  465. fn test_encode_copy_operation_max_length() {
  466. // Test maximum length for version 2 packs (0xFFFF)
  467. let result = encode_copy_operation(0, MAX_COPY_LEN);
  468. assert_eq!(result[0] & 0x80, 0x80); // Copy bit
  469. assert_eq!(result[0] & 0x30, 0x30); // Both length bytes present
  470. assert_eq!(result[1], 0xFF); // Length byte 0
  471. assert_eq!(result[2], 0xFF); // Length byte 1
  472. assert_eq!(result.len(), 3);
  473. }
  474. #[test]
  475. fn test_encode_copy_operation_various_lengths() {
  476. // Test different length values to ensure correct encoding
  477. // Note: only non-zero bytes are encoded
  478. // Length 1: byte0=1 -> only bit 4 set
  479. let result = encode_copy_operation(0, 1);
  480. assert_eq!(result[0] & 0x80, 0x80);
  481. assert_eq!(result[0] & 0x30, 0x10);
  482. assert_eq!(result[1], 1);
  483. // Length 255 (0xFF): byte0=0xFF, byte1=0 -> only bit 4 set
  484. let result = encode_copy_operation(0, 255);
  485. assert_eq!(result[0] & 0x80, 0x80);
  486. assert_eq!(result[0] & 0x30, 0x10);
  487. assert_eq!(result[1], 0xFF);
  488. // Length 256 (0x100): byte0=0, byte1=1 -> only bit 5 set
  489. let result = encode_copy_operation(0, 256);
  490. assert_eq!(result[0] & 0x80, 0x80);
  491. assert_eq!(result[0] & 0x30, 0x20); // Only second length byte
  492. assert_eq!(result[1], 1);
  493. // Length 1000 (0x3E8): byte0=0xE8, byte1=3 -> both bits set
  494. let result = encode_copy_operation(0, 1000);
  495. assert_eq!(result[0] & 0x80, 0x80);
  496. assert_eq!(result[0] & 0x30, 0x30); // Both length bytes
  497. assert_eq!(result[1], 0xE8);
  498. assert_eq!(result[2], 0x03);
  499. // Length 0xFFFF: byte0=0xFF, byte1=0xFF -> both bits set
  500. let result = encode_copy_operation(0, 0xFFFF);
  501. assert_eq!(result[0] & 0x80, 0x80);
  502. assert_eq!(result[0] & 0x30, 0x30); // Both length bytes
  503. assert_eq!(result[1], 0xFF);
  504. assert_eq!(result[2], 0xFF);
  505. }
  506. #[test]
  507. fn test_create_delta_identical() {
  508. // Delta between identical buffers should be minimal
  509. let base = b"hello world";
  510. let target = b"hello world";
  511. let delta = create_delta_internal(base, target);
  512. // Should have header (2 size encodings) plus copy operations
  513. assert!(delta.len() < base.len()); // Delta should be smaller than full data
  514. }
  515. #[test]
  516. fn test_create_delta_completely_different() {
  517. // Delta between completely different buffers
  518. let base = b"aaaaaaaaaa";
  519. let target = b"bbbbbbbbbb";
  520. let delta = create_delta_internal(base, target);
  521. // Should have header plus insert operations with the new data
  522. assert!(delta.len() > 0);
  523. }
  524. #[test]
  525. fn test_create_and_apply_delta() {
  526. // Test that create_delta and apply_delta are inverse operations
  527. let base = b"The quick brown fox jumps over the lazy dog";
  528. let target = b"The quick brown cat jumps over the lazy dog";
  529. // Create delta
  530. let delta = create_delta_internal(base, target);
  531. // Apply delta should reconstruct target
  532. let mut index = 0;
  533. let src_size = get_delta_header_size(&delta, &mut index, delta.len());
  534. assert_eq!(src_size, base.len());
  535. let dest_size = get_delta_header_size(&delta, &mut index, delta.len());
  536. assert_eq!(dest_size, target.len());
  537. // The delta should be valid and smaller than sending the full target
  538. assert!(delta.len() > 0);
  539. }
  540. #[test]
  541. fn test_create_delta_with_insertion() {
  542. let base = b"hello";
  543. let target = b"hello world";
  544. let delta = create_delta_internal(base, target);
  545. // Should have a copy operation for "hello" and insert for " world"
  546. assert!(delta.len() > 0);
  547. }
  548. #[test]
  549. fn test_create_delta_with_deletion() {
  550. let base = b"hello world";
  551. let target = b"hello";
  552. let delta = create_delta_internal(base, target);
  553. // Should have a copy operation for "hello" only
  554. assert!(delta.len() > 0);
  555. }
  556. }
  557. #[pymodule]
  558. fn _pack(_py: Python, m: &Bound<PyModule>) -> PyResult<()> {
  559. m.add_function(wrap_pyfunction!(bisect_find_sha, m)?)?;
  560. m.add_function(wrap_pyfunction!(apply_delta, m)?)?;
  561. m.add_function(wrap_pyfunction!(create_delta, m)?)?;
  562. Ok(())
  563. }