lib.rs 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  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 public 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. use memchr::memchr;
  21. use std::borrow::Cow;
  22. use pyo3::exceptions::PyTypeError;
  23. use pyo3::import_exception;
  24. use pyo3::prelude::*;
  25. use pyo3::types::{PyBytes, PyDict};
  26. import_exception!(dulwich.errors, ObjectFormatException);
  27. const S_IFDIR: u32 = 0o40000;
  28. fn bytehex(byte: u8) -> u8 {
  29. match byte {
  30. 0..=9 => byte + b'0',
  31. 10..=15 => byte - 10 + b'a',
  32. _ => unreachable!(),
  33. }
  34. }
  35. fn sha_to_pyhex(py: Python, sha: &[u8]) -> PyResult<PyObject> {
  36. let mut hexsha = Vec::new();
  37. for c in sha {
  38. hexsha.push(bytehex((c & 0xF0) >> 4));
  39. hexsha.push(bytehex(c & 0x0F));
  40. }
  41. Ok(PyBytes::new_bound(py, hexsha.as_slice()).into())
  42. }
  43. #[pyfunction]
  44. #[pyo3(signature = (text, strict=None))]
  45. fn parse_tree(
  46. py: Python,
  47. mut text: &[u8],
  48. strict: Option<bool>,
  49. ) -> PyResult<Vec<(PyObject, u32, PyObject)>> {
  50. let mut entries = Vec::new();
  51. let strict = strict.unwrap_or(false);
  52. while !text.is_empty() {
  53. let mode_end = match memchr(b' ', text) {
  54. Some(e) => e,
  55. None => {
  56. return Err(ObjectFormatException::new_err((
  57. "Missing terminator for mode",
  58. )));
  59. }
  60. };
  61. let text_str = String::from_utf8_lossy(&text[..mode_end]).to_string();
  62. let mode = match u32::from_str_radix(text_str.as_str(), 8) {
  63. Ok(m) => m,
  64. Err(e) => {
  65. return Err(ObjectFormatException::new_err((format!(
  66. "invalid mode: {}",
  67. e
  68. ),)));
  69. }
  70. };
  71. if strict && text[0] == b'0' {
  72. return Err(ObjectFormatException::new_err((
  73. "Illegal leading zero on mode",
  74. )));
  75. }
  76. text = &text[mode_end + 1..];
  77. let namelen = match memchr(b'\0', text) {
  78. Some(nl) => nl,
  79. None => {
  80. return Err(ObjectFormatException::new_err(("Missing trailing \\0",)));
  81. }
  82. };
  83. let name = &text[..namelen];
  84. if namelen + 20 >= text.len() {
  85. return Err(ObjectFormatException::new_err(("SHA truncated",)));
  86. }
  87. text = &text[namelen + 1..];
  88. let sha = &text[..20];
  89. entries.push((
  90. PyBytes::new_bound(py, name).to_object(py),
  91. mode,
  92. sha_to_pyhex(py, sha)?,
  93. ));
  94. text = &text[20..];
  95. }
  96. Ok(entries)
  97. }
  98. fn name_with_suffix(mode: u32, name: &[u8]) -> Cow<[u8]> {
  99. if mode & S_IFDIR != 0 {
  100. let mut v = name.to_vec();
  101. v.push(b'/');
  102. v.into()
  103. } else {
  104. name.into()
  105. }
  106. }
  107. /// Iterate over a tree entries dictionary.
  108. ///
  109. /// # Arguments
  110. ///
  111. /// name_order: If True, iterate entries in order of their name. If
  112. /// False, iterate entries in tree order, that is, treat subtree entries as
  113. /// having '/' appended.
  114. /// entries: Dictionary mapping names to (mode, sha) tuples
  115. ///
  116. /// # Returns: Iterator over (name, mode, hexsha)
  117. #[pyfunction]
  118. fn sorted_tree_items(py: Python, entries: &Bound<PyDict>, name_order: bool) -> PyResult<Vec<PyObject>> {
  119. let mut qsort_entries = Vec::new();
  120. for (name, e) in entries.iter() {
  121. let (mode, sha): (u32, Vec<u8>) = match e.extract() {
  122. Ok(o) => o,
  123. Err(e) => {
  124. return Err(PyTypeError::new_err((format!("invalid type: {}", e),)));
  125. }
  126. };
  127. qsort_entries.push((name.extract::<Vec<u8>>().unwrap(), mode, sha));
  128. }
  129. if name_order {
  130. qsort_entries.sort_by(|a, b| a.0.cmp(&b.0));
  131. } else {
  132. qsort_entries.sort_by(|a, b| {
  133. name_with_suffix(a.1, a.0.as_slice()).cmp(&name_with_suffix(b.1, b.0.as_slice()))
  134. });
  135. }
  136. let objectsm = py.import_bound("dulwich.objects")?;
  137. let tree_entry_cls = objectsm.getattr("TreeEntry")?;
  138. qsort_entries
  139. .into_iter()
  140. .map(|(name, mode, hexsha)| -> PyResult<PyObject> {
  141. Ok(tree_entry_cls
  142. .call1((
  143. PyBytes::new_bound(py, name.as_slice()).to_object(py),
  144. mode,
  145. PyBytes::new_bound(py, hexsha.as_slice()).to_object(py),
  146. ))?
  147. .to_object(py))
  148. })
  149. .collect::<PyResult<Vec<PyObject>>>()
  150. }
  151. #[pymodule]
  152. fn _objects(_py: Python, m: &Bound<PyModule>) -> PyResult<()> {
  153. m.add_function(wrap_pyfunction!(sorted_tree_items, m)?)?;
  154. m.add_function(wrap_pyfunction!(parse_tree, m)?)?;
  155. Ok(())
  156. }