1use std::{cell::RefCell, cmp::Ordering};
2
3use crate::{
4 bstr::{BStr, BString},
5 tree, Tree, TreeRef,
6};
7
8pub mod editor;
10
11mod ref_iter;
12pub mod write;
14
15#[doc(alias = "TreeUpdateBuilder", alias = "git2")]
23#[derive(Clone)]
24pub struct Editor<'a> {
25 find: &'a dyn crate::FindExt,
27 object_hash: gix_hash::Kind,
29 trees: std::collections::HashMap<BString, Tree>,
33 path_buf: RefCell<BString>,
35 tree_buf: Vec<u8>,
37}
38
39#[derive(Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)]
46#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
47pub struct EntryMode {
48 internal: u16,
51}
52
53impl TryFrom<u32> for tree::EntryMode {
54 type Error = u32;
55 fn try_from(mode: u32) -> Result<Self, Self::Error> {
56 Ok(match mode {
57 0o40000 | 0o120000 | 0o160000 => EntryMode { internal: mode as u16 },
58 blob_mode if blob_mode & 0o100000 == 0o100000 => EntryMode { internal: mode as u16 },
59 _ => return Err(mode),
60 })
61 }
62}
63
64impl EntryMode {
65 pub const fn value(self) -> u16 {
67 if self.internal & IFMT == 0o140000 {
70 0o040000
71 } else {
72 self.internal
73 }
74 }
75
76 pub fn as_bytes<'a>(&self, backing: &'a mut [u8; 6]) -> &'a BStr {
79 if self.internal == 0 {
80 std::slice::from_ref(&b'0')
81 } else {
82 for (idx, backing_octet) in backing.iter_mut().enumerate() {
83 let bit_pos = 3 * (6 - idx - 1);
84 let oct_mask = 0b111 << bit_pos;
85 let digit = (self.internal & oct_mask) >> bit_pos;
86 *backing_octet = b'0' + digit as u8;
87 }
88 if backing[1] == b'4' {
90 if backing[0] == b'1' {
91 backing[0] = b'0';
92 &backing[0..6]
93 } else {
94 &backing[1..6]
95 }
96 } else {
97 &backing[0..6]
98 }
99 }
100 .into()
101 }
102
103 pub(crate) fn extract_from_bytes(i: &[u8]) -> Option<(Self, &'_ [u8])> {
106 let mut mode = 0;
107 let mut idx = 0;
108 let mut space_pos = 0;
109 if i.is_empty() {
110 return None;
111 }
112 while idx < i.len() {
114 let b = i[idx];
115 if b == b' ' {
117 space_pos = idx;
118 break;
119 }
120 #[allow(clippy::manual_range_contains)]
123 if b < b'0' || b > b'7' {
124 return None;
125 }
126 if idx > 6 {
128 return None;
129 }
130 mode = (mode << 3) + (b - b'0') as u16;
131 idx += 1;
132 }
133 if mode == 0o040000 && i[0] == b'0' {
135 mode += 0o100000;
136 }
137 Some((Self { internal: mode }, &i[(space_pos + 1)..]))
138 }
139
140 pub fn from_bytes(i: &[u8]) -> Option<Self> {
142 Self::extract_from_bytes(i).map(|(mode, _rest)| mode)
143 }
144}
145
146impl std::fmt::Debug for EntryMode {
147 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148 write!(f, "EntryMode(0o{})", self.as_bytes(&mut Default::default()))
149 }
150}
151
152impl std::fmt::Octal for EntryMode {
153 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154 write!(f, "{}", self.as_bytes(&mut Default::default()))
155 }
156}
157
158#[derive(Clone, Copy, PartialEq, Eq, Debug, Ord, PartialOrd, Hash)]
163#[repr(u16)]
164#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
165pub enum EntryKind {
166 Tree = 0o040000u16,
168 Blob = 0o100644,
170 BlobExecutable = 0o100755,
172 Link = 0o120000,
174 Commit = 0o160000,
176}
177
178impl From<EntryKind> for EntryMode {
179 fn from(value: EntryKind) -> Self {
180 EntryMode { internal: value as u16 }
181 }
182}
183
184impl From<EntryMode> for EntryKind {
185 fn from(value: EntryMode) -> Self {
186 value.kind()
187 }
188}
189
190impl EntryKind {
192 pub fn as_octal_str(&self) -> &'static BStr {
194 use EntryKind::*;
195 let bytes: &[u8] = match self {
196 Tree => b"40000",
197 Blob => b"100644",
198 BlobExecutable => b"100755",
199 Link => b"120000",
200 Commit => b"160000",
201 };
202 bytes.into()
203 }
204}
205
206const IFMT: u16 = 0o170000;
207
208impl EntryMode {
209 pub const fn kind(&self) -> EntryKind {
211 let etype = self.value() & IFMT;
212 if etype == 0o100000 {
213 if self.value() & 0o000100 == 0o000100 {
214 EntryKind::BlobExecutable
215 } else {
216 EntryKind::Blob
217 }
218 } else if etype == EntryKind::Link as u16 {
219 EntryKind::Link
220 } else if etype == EntryKind::Tree as u16 {
221 EntryKind::Tree
222 } else {
223 EntryKind::Commit
224 }
225 }
226
227 pub const fn is_tree(&self) -> bool {
229 self.value() & IFMT == EntryKind::Tree as u16
230 }
231
232 pub const fn is_commit(&self) -> bool {
234 self.value() & IFMT == EntryKind::Commit as u16
235 }
236
237 pub const fn is_link(&self) -> bool {
239 self.value() & IFMT == EntryKind::Link as u16
240 }
241
242 pub const fn is_no_tree(&self) -> bool {
244 self.value() & IFMT != EntryKind::Tree as u16
245 }
246
247 pub const fn is_blob(&self) -> bool {
249 self.value() & IFMT == 0o100000
250 }
251
252 pub const fn is_executable(&self) -> bool {
254 matches!(self.kind(), EntryKind::BlobExecutable)
255 }
256
257 pub const fn is_blob_or_symlink(&self) -> bool {
259 matches!(
260 self.kind(),
261 EntryKind::Blob | EntryKind::BlobExecutable | EntryKind::Link
262 )
263 }
264
265 pub const fn as_str(&self) -> &'static str {
267 use EntryKind::*;
268 match self.kind() {
269 Tree => "tree",
270 Blob => "blob",
271 BlobExecutable => "exe",
272 Link => "link",
273 Commit => "commit",
274 }
275 }
276}
277
278impl TreeRef<'_> {
279 pub fn to_owned(&self) -> Tree {
284 self.clone().into()
285 }
286
287 pub fn into_owned(self) -> Tree {
289 self.into()
290 }
291}
292
293#[derive(PartialEq, Eq, Debug, Hash, Clone, Copy)]
295#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
296pub struct EntryRef<'a> {
297 pub mode: tree::EntryMode,
299 pub filename: &'a BStr,
301 #[cfg_attr(feature = "serde", serde(borrow))]
305 pub oid: &'a gix_hash::oid,
306}
307
308impl PartialOrd for EntryRef<'_> {
309 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
310 Some(self.cmp(other))
311 }
312}
313
314impl Ord for EntryRef<'_> {
315 fn cmp(&self, b: &Self) -> Ordering {
316 let a = self;
317 let common = a.filename.len().min(b.filename.len());
318 a.filename[..common].cmp(&b.filename[..common]).then_with(|| {
319 let a = a.filename.get(common).or_else(|| a.mode.is_tree().then_some(&b'/'));
320 let b = b.filename.get(common).or_else(|| b.mode.is_tree().then_some(&b'/'));
321 a.cmp(&b)
322 })
323 }
324}
325
326#[derive(PartialEq, Eq, Debug, Hash, Clone)]
328#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
329pub struct Entry {
330 pub mode: EntryMode,
332 pub filename: BString,
334 pub oid: gix_hash::ObjectId,
336}
337
338impl PartialOrd for Entry {
339 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
340 Some(self.cmp(other))
341 }
342}
343
344impl Ord for Entry {
345 fn cmp(&self, b: &Self) -> Ordering {
346 let a = self;
347 let common = a.filename.len().min(b.filename.len());
348 a.filename[..common].cmp(&b.filename[..common]).then_with(|| {
349 let a = a.filename.get(common).or_else(|| a.mode.is_tree().then_some(&b'/'));
350 let b = b.filename.get(common).or_else(|| b.mode.is_tree().then_some(&b'/'));
351 a.cmp(&b)
352 })
353 }
354}