1use std::{cell::RefCell, cmp::Ordering};
2
3use crate::{
4 Tree, TreeRef,
5 bstr::{BStr, BString},
6 tree,
7};
8
9pub mod editor;
11
12mod ref_iter;
13pub use ref_iter::next_entry;
14
15pub fn name_order(a: &[u8], a_is_tree: bool, b: &[u8], b_is_tree: bool) -> Ordering {
20 let common = a.len().min(b.len());
21 a[..common].cmp(&b[..common]).then_with(|| {
22 let a = a.get(common).or_else(|| a_is_tree.then_some(&b'/'));
23 let b = b.get(common).or_else(|| b_is_tree.then_some(&b'/'));
24 a.cmp(&b)
25 })
26}
27
28pub mod write;
30
31#[doc(alias = "TreeUpdateBuilder", alias = "git2")]
39#[derive(Clone)]
40pub struct Editor<'a> {
41 find: &'a dyn crate::FindExt,
43 object_hash: gix_hash::Kind,
45 trees: std::collections::HashMap<BString, Tree>,
49 path_buf: RefCell<BString>,
51 tree_buf: Vec<u8>,
53}
54
55#[derive(Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)]
62#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
63pub struct EntryMode {
64 internal: u16,
67}
68
69impl TryFrom<u32> for tree::EntryMode {
70 type Error = u32;
71 fn try_from(mode: u32) -> Result<Self, Self::Error> {
72 Ok(match mode {
73 0o40000 | 0o120000 | 0o160000 => EntryMode { internal: mode as u16 },
74 blob_mode if blob_mode & 0o100000 == 0o100000 => EntryMode { internal: mode as u16 },
75 _ => return Err(mode),
76 })
77 }
78}
79
80impl EntryMode {
81 pub const fn value(self) -> u16 {
83 if self.internal & IFMT == 0o140000 {
86 0o040000
87 } else {
88 self.internal
89 }
90 }
91
92 pub fn as_bytes<'a>(&self, backing: &'a mut [u8; 6]) -> &'a BStr {
95 if self.internal == 0 {
96 std::slice::from_ref(&b'0')
97 } else {
98 for (idx, backing_octet) in backing.iter_mut().enumerate() {
99 let bit_pos = 3 * (6 - idx - 1);
100 let oct_mask = 0b111 << bit_pos;
101 let digit = (self.internal & oct_mask) >> bit_pos;
102 *backing_octet = b'0' + digit as u8;
103 }
104 if backing[1] == b'4' {
106 if backing[0] == b'1' {
107 backing[0] = b'0';
108 &backing[0..6]
109 } else {
110 &backing[1..6]
111 }
112 } else {
113 &backing[0..6]
114 }
115 }
116 .into()
117 }
118
119 pub(crate) fn extract_from_bytes(i: &[u8]) -> Option<(Self, &'_ [u8])> {
122 let mut mode = 0;
123 if i.is_empty() {
124 return None;
125 }
126
127 let space_pos = if i.get(6) == Some(&b' ') && i.get(5) != Some(&b' ') {
129 for b in i.iter().take(6) {
130 let b = b.wrapping_sub(b'0') as u16;
131 if b > 7 {
134 return None;
135 }
136 mode = (mode << 3) + b;
137 }
138 6
139 }
140 else {
142 let mut idx = 0;
143 let mut space_pos = 0;
144
145 while idx < i.len() {
147 let b = i[idx].wrapping_sub(b'0') as u16;
148 if b == b' '.wrapping_sub(b'0') as u16 {
150 space_pos = idx;
151 break;
152 }
153 if b > 7 {
156 return None;
157 }
158 if idx > 6 {
160 return None;
161 }
162 mode = (mode << 3) + b;
163 idx += 1;
164 }
165
166 space_pos
167 };
168
169 if mode == 0o040000 && i[0] == b'0' {
171 mode += 0o100000;
172 }
173 Some((Self { internal: mode }, &i[(space_pos + 1)..]))
174 }
175
176 pub fn from_bytes(i: &[u8]) -> Option<Self> {
178 Self::extract_from_bytes(i).map(|(mode, _rest)| mode)
179 }
180}
181
182impl std::fmt::Debug for EntryMode {
183 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184 write!(f, "EntryMode(0o{})", self.as_bytes(&mut Default::default()))
185 }
186}
187
188impl std::fmt::Octal for EntryMode {
189 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
190 write!(f, "{}", self.as_bytes(&mut Default::default()))
191 }
192}
193
194#[derive(Clone, Copy, PartialEq, Eq, Debug, Ord, PartialOrd, Hash)]
199#[repr(u16)]
200#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
201pub enum EntryKind {
202 Tree = 0o040000u16,
204 Blob = 0o100644,
206 BlobExecutable = 0o100755,
208 Link = 0o120000,
210 Commit = 0o160000,
212}
213
214impl From<EntryKind> for EntryMode {
215 fn from(value: EntryKind) -> Self {
216 EntryMode { internal: value as u16 }
217 }
218}
219
220impl From<EntryMode> for EntryKind {
221 fn from(value: EntryMode) -> Self {
222 value.kind()
223 }
224}
225
226impl EntryKind {
228 pub fn as_octal_str(&self) -> &'static BStr {
230 use EntryKind::*;
231 let bytes: &[u8] = match self {
232 Tree => b"40000",
233 Blob => b"100644",
234 BlobExecutable => b"100755",
235 Link => b"120000",
236 Commit => b"160000",
237 };
238 bytes.into()
239 }
240}
241
242const IFMT: u16 = 0o170000;
243
244impl EntryMode {
245 pub const fn kind(&self) -> EntryKind {
247 let etype = self.value() & IFMT;
248 if etype == 0o100000 {
249 if self.value() & 0o000100 == 0o000100 {
250 EntryKind::BlobExecutable
251 } else {
252 EntryKind::Blob
253 }
254 } else if etype == EntryKind::Link as u16 {
255 EntryKind::Link
256 } else if etype == EntryKind::Tree as u16 {
257 EntryKind::Tree
258 } else {
259 EntryKind::Commit
260 }
261 }
262
263 pub const fn is_tree(&self) -> bool {
265 self.value() & IFMT == EntryKind::Tree as u16
266 }
267
268 pub const fn is_commit(&self) -> bool {
270 self.value() & IFMT == EntryKind::Commit as u16
271 }
272
273 pub const fn is_link(&self) -> bool {
275 self.value() & IFMT == EntryKind::Link as u16
276 }
277
278 pub const fn is_no_tree(&self) -> bool {
280 self.value() & IFMT != EntryKind::Tree as u16
281 }
282
283 pub const fn is_blob(&self) -> bool {
285 self.value() & IFMT == 0o100000
286 }
287
288 pub const fn is_executable(&self) -> bool {
290 matches!(self.kind(), EntryKind::BlobExecutable)
291 }
292
293 pub const fn is_blob_or_symlink(&self) -> bool {
295 matches!(
296 self.kind(),
297 EntryKind::Blob | EntryKind::BlobExecutable | EntryKind::Link
298 )
299 }
300
301 pub const fn as_str(&self) -> &'static str {
303 use EntryKind::*;
304 match self.kind() {
305 Tree => "tree",
306 Blob => "blob",
307 BlobExecutable => "exe",
308 Link => "link",
309 Commit => "commit",
310 }
311 }
312}
313
314impl TreeRef<'_> {
315 pub fn to_owned(&self) -> Tree {
320 self.clone().into()
321 }
322
323 pub fn into_owned(self) -> Tree {
325 self.into()
326 }
327}
328
329#[derive(PartialEq, Eq, Debug, Hash, Clone, Copy)]
331#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
332pub struct EntryRef<'a> {
333 pub mode: tree::EntryMode,
335 pub filename: &'a BStr,
337 #[cfg_attr(feature = "serde", serde(borrow))]
341 pub oid: &'a gix_hash::oid,
342}
343
344impl PartialOrd for EntryRef<'_> {
345 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
346 Some(self.cmp(other))
347 }
348}
349
350impl Ord for EntryRef<'_> {
351 fn cmp(&self, b: &Self) -> Ordering {
352 name_order(self.filename, self.mode.is_tree(), b.filename, b.mode.is_tree())
353 }
354}
355
356#[derive(PartialEq, Eq, Debug, Hash, Clone)]
358#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
359pub struct Entry {
360 pub mode: EntryMode,
362 pub filename: BString,
364 pub oid: gix_hash::ObjectId,
366}
367
368impl PartialOrd for Entry {
369 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
370 Some(self.cmp(other))
371 }
372}
373
374impl Ord for Entry {
375 fn cmp(&self, b: &Self) -> Ordering {
376 name_order(&self.filename, self.mode.is_tree(), &b.filename, b.mode.is_tree())
377 }
378}