lib.rs 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. /*
  2. * Copyright (C) 2010 Google, Inc.
  3. * Copyright (C) 2024 Jelmer Vernooij <jelmer@jelmer.uk>
  4. *
  5. * Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
  6. * General Public License as public by the Free Software Foundation; version 2.0
  7. * or (at your option) any later version. You can redistribute it and/or
  8. * modify it under the terms of either of these two licenses.
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. *
  16. * You should have received a copy of the licenses; if not, see
  17. * <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
  18. * and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
  19. * License, Version 2.0.
  20. */
  21. use pyo3::prelude::*;
  22. use pyo3::types::{PyBytes, PyList, PyTuple};
  23. use pyo3::exceptions::{PyTypeError};
  24. use pyo3::Python;
  25. use std::cmp::Ordering;
  26. const S_IFMT: u32 = 0o170000;
  27. const S_IFDIR: u32 = 0o040000;
  28. fn add_hash(
  29. get: &PyAny,
  30. set: &PyAny,
  31. string: &[u8],
  32. py: Python,
  33. ) -> PyResult<()> {
  34. let str_obj = PyBytes::new(py, string);
  35. let hash_obj = str_obj.hash()?;
  36. let value = get.call1((hash_obj,))?;
  37. let n = string.len();
  38. set.call1((hash_obj, value.extract::<usize>()? + n))?;
  39. Ok(())
  40. }
  41. #[pyfunction]
  42. fn _count_blocks(py: Python, obj: &PyAny) -> PyResult<PyObject> {
  43. let default_dict_cls = PyModule::import(py, "collections")?.getattr("defaultdict")?;
  44. let int_cls = PyModule::import(py, "builtins")?.getattr("int")?;
  45. let counts = default_dict_cls.call1((int_cls,))?;
  46. let get = counts.getattr("__getitem__")?;
  47. let set = counts.getattr("__setitem__")?;
  48. let chunks = obj.call_method0("as_raw_chunks")?;
  49. if !chunks.is_instance_of::<PyList>() {
  50. return Err(PyTypeError::new_err("as_raw_chunks() did not return a list"));
  51. }
  52. let num_chunks = chunks.extract::<Vec<PyObject>>()?.len();
  53. let pym = py.import("dulwich.diff_tree")?;
  54. let block_size = pym.getattr("_BLOCK_SIZE")?.extract::<usize>()?;
  55. let mut block: Vec<u8> = Vec::with_capacity(block_size);
  56. for i in 0..num_chunks {
  57. let chunk = chunks.get_item(i)?;
  58. if !chunk.is_instance_of::<PyBytes>() {
  59. return Err(PyTypeError::new_err("chunk is not a string"));
  60. }
  61. let chunk_str = chunk.extract::<&[u8]>()?;
  62. for c in chunk_str {
  63. block.push(*c);
  64. if *c == b'\n' || block.len() == block_size {
  65. add_hash(get, set, &block, py)?;
  66. block.clear();
  67. }
  68. }
  69. }
  70. if !block.is_empty() {
  71. add_hash(get, set, &block, py)?;
  72. }
  73. Ok(counts.to_object(py))
  74. }
  75. #[pyfunction]
  76. fn _is_tree(_py: Python, entry: &PyAny) -> PyResult<bool> {
  77. let mode = entry.getattr("mode")?;
  78. if mode.is_none() {
  79. Ok(false)
  80. } else {
  81. let lmode = mode.extract::<u32>()?;
  82. Ok((lmode & S_IFMT) == S_IFDIR)
  83. }
  84. }
  85. fn tree_entries(
  86. path: &[u8],
  87. tree: &PyAny,
  88. py: Python,
  89. ) -> PyResult<Vec<PyObject>> {
  90. if tree.is_none() {
  91. return Ok(Vec::new());
  92. }
  93. let dom = py.import("dulwich.objects")?;
  94. let tree_entry_cls = dom.getattr("TreeEntry")?;
  95. let items = tree.call_method1("iteritems", (true,))?.extract::<Vec<PyObject>>()?;
  96. let mut result = Vec::new();
  97. for item in items {
  98. let (name, mode, sha) = item.extract::<(&[u8], u32, PyObject)>(py)?;
  99. let mut new_path = Vec::with_capacity(path.len() + name.len() + 1);
  100. if !path.is_empty() {
  101. new_path.extend_from_slice(path);
  102. new_path.push(b'/');
  103. }
  104. new_path.extend_from_slice(name);
  105. let tree_entry = tree_entry_cls.call1((PyBytes::new(py, &new_path), mode, sha))?;
  106. result.push(tree_entry.to_object(py));
  107. }
  108. Ok(result)
  109. }
  110. fn entry_path_cmp(entry1: &PyAny, entry2: &PyAny) -> PyResult<Ordering> {
  111. let path1 = entry1.getattr("path")?.extract::<&[u8]>()?;
  112. let path2 = entry2.getattr("path")?.extract::<&[u8]>()?;
  113. Ok(path1.cmp(path2))
  114. }
  115. #[pyfunction]
  116. fn _merge_entries(py: Python, path: &[u8], tree1: &PyAny, tree2: &PyAny) -> PyResult<PyObject> {
  117. let entries1 = tree_entries(path, tree1, py)?;
  118. let entries2 = tree_entries(path, tree2, py)?;
  119. let pym = py.import("dulwich.diff_tree")?;
  120. let null_entry = pym.getattr("_NULL_ENTRY")?.to_object(py);
  121. let mut result = Vec::new();
  122. let mut i1 = 0;
  123. let mut i2 = 0;
  124. while i1 < entries1.len() && i2 < entries2.len() {
  125. let cmp = entry_path_cmp(entries1[i1].as_ref(py), entries2[i2].as_ref(py))?;
  126. let (e1, e2) = match cmp {
  127. Ordering::Equal => {
  128. (entries1[i1].clone(), entries2[i2].clone())
  129. }
  130. Ordering::Less => {
  131. (entries1[i1].clone(), null_entry.clone())
  132. }
  133. Ordering::Greater => {
  134. (null_entry.clone(), entries2[i2].clone())
  135. }
  136. };
  137. let pair = PyTuple::new(py, &[e1, e2]);
  138. result.push(pair);
  139. match cmp {
  140. Ordering::Equal => {
  141. i1 += 1;
  142. i2 += 1;
  143. }
  144. Ordering::Less => {
  145. i1 += 1;
  146. }
  147. Ordering::Greater => {
  148. i2 += 1;
  149. }
  150. }
  151. }
  152. while i1 < entries1.len() {
  153. let pair = PyTuple::new(py, &[entries1[i1].clone(), null_entry.clone()]);
  154. result.push(pair);
  155. i1 += 1;
  156. }
  157. while i2 < entries2.len() {
  158. let pair = PyTuple::new(py, &[null_entry.clone(), entries2[i2].clone()]);
  159. result.push(pair);
  160. i2 += 1;
  161. }
  162. Ok(PyList::new(py, &result).to_object(py))
  163. }
  164. #[pymodule]
  165. fn _diff_tree(_py: Python, m: &PyModule) -> PyResult<()> {
  166. m.add_function(wrap_pyfunction!(_count_blocks, m)?)?;
  167. m.add_function(wrap_pyfunction!(_is_tree, m)?)?;
  168. m.add_function(wrap_pyfunction!(_merge_entries, m)?)?;
  169. Ok(())
  170. }