1use std::{
2 cmp::Ordering,
3 collections::{HashMap, hash_map},
4 fmt::Formatter,
5};
6
7use bstr::{BStr, BString, ByteSlice, ByteVec};
8use gix_error::{Result, bail, validation};
9use gix_hash::ObjectId;
10
11use crate::{
12 Tree, tree,
13 tree::{Editor, EntryKind},
14};
15
16pub struct Cursor<'a, 'find> {
18 parent: &'a mut Editor<'find>,
20 prefix: BString,
23}
24
25impl std::fmt::Debug for Editor<'_> {
26 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
27 f.debug_struct("Editor")
28 .field("object_hash", &self.object_hash)
29 .field("path_buf", &self.path_buf)
30 .field("trees", &self.trees)
31 .finish()
32 }
33}
34
35impl<'a> Editor<'a> {
37 pub fn new(root: Tree, find: &'a dyn crate::FindExt, object_hash: gix_hash::Kind) -> Self {
42 Editor {
43 find,
44 object_hash,
45 trees: HashMap::from_iter(Some((empty_path(), root))),
46 path_buf: BString::from(Vec::with_capacity(256)).into(),
47 tree_buf: Vec::with_capacity(512),
48 }
49 }
50}
51
52impl Editor<'_> {
54 pub fn write<E>(
73 &mut self,
74 out: impl FnMut(&Tree) -> std::result::Result<ObjectId, E>,
75 ) -> std::result::Result<ObjectId, E> {
76 self.path_buf.borrow_mut().clear();
77 self.write_at_pathbuf(out, WriteMode::Normal)
78 }
79
80 pub fn remove<I, C>(&mut self, rela_path: I) -> Result<&mut Self>
83 where
84 I: IntoIterator<Item = C>,
85 C: AsRef<BStr>,
86 {
87 self.path_buf.borrow_mut().clear();
88 self.upsert_or_remove_at_pathbuf(rela_path, EditMode::Remove(RemoveMode::Any))
89 }
90
91 pub fn remove_leaf<I, C>(&mut self, rela_path: I) -> Result<&mut Self>
98 where
99 I: IntoIterator<Item = C>,
100 C: AsRef<BStr>,
101 {
102 self.path_buf.borrow_mut().clear();
103 self.upsert_or_remove_at_pathbuf(rela_path, EditMode::Remove(RemoveMode::LeafOnly))
104 }
105
106 pub fn remove_if_leaf<I, C>(&mut self, rela_path: I) -> Result<&mut Self>
112 where
113 I: IntoIterator<Item = C>,
114 C: AsRef<BStr>,
115 {
116 self.path_buf.borrow_mut().clear();
117 self.upsert_or_remove_at_pathbuf(rela_path, EditMode::Remove(RemoveMode::IfLeaf))
118 }
119
120 pub fn get<I, C>(&self, rela_path: I) -> Option<&tree::Entry>
126 where
127 I: IntoIterator<Item = C>,
128 C: AsRef<BStr>,
129 {
130 self.path_buf.borrow_mut().clear();
131 self.get_inner(rela_path)
132 }
133
134 pub fn upsert<I, C>(&mut self, rela_path: I, kind: EntryKind, id: ObjectId) -> Result<&mut Self>
149 where
150 I: IntoIterator<Item = C>,
151 C: AsRef<BStr>,
152 {
153 self.path_buf.borrow_mut().clear();
154 self.upsert_or_remove_at_pathbuf(rela_path, EditMode::Upsert(kind, id, UpsertMode::Normal))
155 }
156
157 fn get_inner<I, C>(&self, rela_path: I) -> Option<&tree::Entry>
158 where
159 I: IntoIterator<Item = C>,
160 C: AsRef<BStr>,
161 {
162 let mut path_buf = self.path_buf.borrow_mut();
163 let mut cursor = self.trees.get(path_buf.as_bstr()).expect("root is always present");
164 let mut rela_path = rela_path.into_iter().peekable();
165 while let Some(name) = rela_path.next() {
166 let name = name.as_ref();
167 let is_last = rela_path.peek().is_none();
168 match cursor
169 .entries
170 .binary_search_by(|e| cmp_entry_with_name(e, name, true))
171 .or_else(|_| cursor.entries.binary_search_by(|e| cmp_entry_with_name(e, name, false)))
172 {
173 Ok(idx) if is_last => return Some(&cursor.entries[idx]),
174 Ok(idx) => {
175 if cursor.entries[idx].mode.is_tree() {
176 push_path_component(&mut path_buf, name);
177 cursor = self.trees.get(path_buf.as_bstr())?;
178 } else {
179 break;
180 }
181 }
182 Err(_) => break,
183 }
184 }
185 None
186 }
187
188 fn write_at_pathbuf<E>(
189 &mut self,
190 mut out: impl FnMut(&Tree) -> std::result::Result<ObjectId, E>,
191 mode: WriteMode,
192 ) -> std::result::Result<ObjectId, E> {
193 assert_ne!(self.trees.len(), 0, "there is at least the root tree");
194
195 let path_buf = self.path_buf.borrow_mut();
197 let mut parents = vec![(
198 None::<usize>,
199 path_buf.clone(),
200 self.trees
201 .remove(path_buf.as_bstr())
202 .expect("root tree is always present"),
203 )];
204 let mut children = Vec::new();
205 while let Some((parent_idx, mut rela_path, mut tree)) = children.pop().or_else(|| parents.pop()) {
206 let mut all_entries_unchanged_or_written = true;
207 for entry in &tree.entries {
208 if entry.mode.is_tree() {
209 let prev_len = push_path_component(&mut rela_path, &entry.filename);
210 if let Some(sub_tree) = self.trees.remove(&rela_path) {
211 all_entries_unchanged_or_written = false;
212 let next_parent_idx = parents.len();
213 children.push((Some(next_parent_idx), rela_path.clone(), sub_tree));
214 }
215 rela_path.truncate(prev_len);
216 }
217 }
218 if all_entries_unchanged_or_written {
219 tree.entries.retain(|e| !e.oid.is_null());
220 if let Some((_, _, parent_to_adjust)) =
221 parent_idx.map(|idx| parents.get_mut(idx).expect("always present, pointing towards zero"))
222 {
223 let name = filename(rela_path.as_bstr());
224 let entry_idx = parent_to_adjust
225 .entries
226 .binary_search_by(|e| cmp_entry_with_name(e, name, true))
227 .expect("the parent always knows us by name");
228 if tree.entries.is_empty() {
229 parent_to_adjust.entries.remove(entry_idx);
230 } else {
231 match out(&tree) {
232 Ok(id) => {
233 parent_to_adjust.entries[entry_idx].oid = id;
234 }
235 Err(err) => {
236 let root_tree = parents.into_iter().next().expect("root wasn't consumed yet");
237 self.trees.insert(root_tree.1, root_tree.2);
238 return Err(err);
239 }
240 }
241 }
242 } else if parents.is_empty() {
243 debug_assert!(children.is_empty(), "we consume children before parents");
244 debug_assert_eq!(rela_path, **path_buf, "this should always be the root tree");
245
246 match out(&tree) {
248 Ok(id) => {
249 let root_tree_id = id;
250 match mode {
251 WriteMode::Normal => {
252 self.trees.clear();
253 }
254 WriteMode::FromCursor => {}
255 }
256 self.trees.insert(rela_path, tree);
257 return Ok(root_tree_id);
258 }
259 Err(err) => {
260 self.trees.insert(rela_path, tree);
261 return Err(err);
262 }
263 }
264 } else if !tree.entries.is_empty() {
265 out(&tree)?;
266 }
267 } else {
268 parents.push((parent_idx, rela_path, tree));
269 }
270 }
271
272 unreachable!("we exit as soon as everything is consumed")
273 }
274
275 fn upsert_or_remove_at_pathbuf<I, C>(&mut self, rela_path: I, edit: EditMode) -> Result<&mut Self>
276 where
277 I: IntoIterator<Item = C>,
278 C: AsRef<BStr>,
279 {
280 let mut path_buf = self.path_buf.borrow_mut();
281 let mut cursor = self.trees.get_mut(path_buf.as_bstr()).expect("root is always present");
282 let mut rela_path = rela_path.into_iter().peekable();
283 let new_kind_is_tree = matches!(edit, EditMode::Upsert(EntryKind::Tree, _, _));
284 while let Some(name) = rela_path.next() {
285 let name = name.as_ref();
286 if name.is_empty() {
287 bail!(validation("Empty path components are not allowed"));
288 }
289 let is_last = rela_path.peek().is_none();
290 let mut needs_sorting = false;
291 let current_level_must_be_tree = !is_last || new_kind_is_tree;
292 let check_type_change = |entry: &tree::Entry| entry.mode.is_tree() != current_level_must_be_tree;
293 let tree_to_lookup = match cursor
294 .entries
295 .binary_search_by(|e| cmp_entry_with_name(e, name, false))
296 .or_else(|file_insertion_idx| {
297 cursor
298 .entries
299 .binary_search_by(|e| cmp_entry_with_name(e, name, true))
300 .map_err(|dir_insertion_index| {
301 if current_level_must_be_tree {
302 dir_insertion_index
303 } else {
304 file_insertion_idx
305 }
306 })
307 }) {
308 Ok(idx) => {
309 match edit {
310 EditMode::Remove(mode) => {
311 if is_last {
312 if cursor.entries[idx].mode.is_tree() {
313 match mode {
314 RemoveMode::Any => {}
315 RemoveMode::IfLeaf => break,
316 RemoveMode::LeafOnly => {
317 let rela_path = path_with_component(path_buf.as_bstr(), name);
318 bail!(validation(format!(
319 "Cannot remove \"{rela_path}\" as leaf entry because it is a tree"
320 )));
321 }
322 }
323 }
324 cursor.entries.remove(idx);
325 break;
326 } else {
327 let entry = &cursor.entries[idx];
328 if entry.mode.is_tree() {
329 Some(entry.oid)
330 } else {
331 break;
332 }
333 }
334 }
335 EditMode::Upsert(kind, id, _mode) => {
336 let entry = &mut cursor.entries[idx];
337 if is_last {
338 entry.oid = id;
340 needs_sorting = check_type_change(entry);
341 entry.mode = kind.into();
342 None
343 } else if entry.mode.is_tree() {
344 Some(entry.oid)
346 } else {
347 entry.oid = id.kind().null();
349 needs_sorting = check_type_change(entry);
350 entry.mode = EntryKind::Tree.into();
351 None
352 }
353 }
354 }
355 }
356 Err(insertion_idx) => match edit {
357 EditMode::Remove(_) => break,
358 EditMode::Upsert(kind, id, _mode) => {
359 cursor.entries.insert(
360 insertion_idx,
361 tree::Entry {
362 filename: name.into(),
363 mode: if is_last { kind.into() } else { EntryKind::Tree.into() },
364 oid: if is_last { id } else { id.kind().null() },
365 },
366 );
367 None
368 }
369 },
370 };
371 if needs_sorting {
372 cursor.entries.sort();
373 }
374 if is_last && matches!(edit, EditMode::Upsert(_, _, UpsertMode::Normal)) {
375 break;
376 }
377 let stop_at_unedited_empty_tree =
378 matches!(edit, EditMode::Remove(RemoveMode::LeafOnly | RemoveMode::IfLeaf))
379 && tree_to_lookup.is_some_and(|id| id.is_empty_tree());
380 push_path_component(&mut path_buf, name);
381 cursor = match self.trees.entry(path_buf.clone()) {
382 hash_map::Entry::Occupied(e) => e.into_mut(),
383 hash_map::Entry::Vacant(_) if stop_at_unedited_empty_tree => break,
384 hash_map::Entry::Vacant(e) => e.insert(
385 if let Some(tree_id) = tree_to_lookup.filter(|tree_id| !tree_id.is_empty_tree()) {
386 self.find.find_tree(&tree_id, &mut self.tree_buf)?.into()
387 } else {
388 Tree::default()
389 },
390 ),
391 };
392 }
393 drop(path_buf);
394 Ok(self)
395 }
396
397 pub fn set_root(&mut self, root: Tree) -> &mut Self {
403 self.trees.clear();
404 self.trees.insert(empty_path(), root);
405 self
406 }
407}
408
409mod cursor {
410 use bstr::{BStr, BString};
411
412 use gix_error::Result;
413 use gix_hash::ObjectId;
414
415 use crate::{
416 Tree, tree,
417 tree::{
418 Editor, EntryKind,
419 editor::{Cursor, EditMode, RemoveMode, UpsertMode, WriteMode},
420 },
421 };
422
423 impl<'a> Editor<'a> {
425 pub fn to_cursor(&mut self) -> Cursor<'_, 'a> {
429 Cursor {
430 parent: self,
431 prefix: BString::default(),
432 }
433 }
434
435 pub fn cursor_at<I, C>(&mut self, rela_path: I) -> Result<Cursor<'_, 'a>>
440 where
441 I: IntoIterator<Item = C>,
442 C: AsRef<BStr>,
443 {
444 self.path_buf.borrow_mut().clear();
445 self.upsert_or_remove_at_pathbuf(
446 rela_path,
447 EditMode::Upsert(EntryKind::Tree, self.object_hash.null(), UpsertMode::AssureTreeOnly),
448 )?;
449 let prefix = self.path_buf.borrow_mut().clone();
450 Ok(Cursor {
451 prefix, parent: self,
453 })
454 }
455 }
456
457 impl Cursor<'_, '_> {
458 pub fn get<I, C>(&self, rela_path: I) -> Option<&tree::Entry>
464 where
465 I: IntoIterator<Item = C>,
466 C: AsRef<BStr>,
467 {
468 self.parent.path_buf.borrow_mut().clone_from(&self.prefix);
469 self.parent.get_inner(rela_path)
470 }
471
472 pub fn upsert<I, C>(&mut self, rela_path: I, kind: EntryKind, id: ObjectId) -> Result<&mut Self>
474 where
475 I: IntoIterator<Item = C>,
476 C: AsRef<BStr>,
477 {
478 self.parent.path_buf.borrow_mut().clone_from(&self.prefix);
479 self.parent
480 .upsert_or_remove_at_pathbuf(rela_path, EditMode::Upsert(kind, id, UpsertMode::Normal))?;
481 Ok(self)
482 }
483
484 pub fn remove<I, C>(&mut self, rela_path: I) -> Result<&mut Self>
486 where
487 I: IntoIterator<Item = C>,
488 C: AsRef<BStr>,
489 {
490 self.parent.path_buf.borrow_mut().clone_from(&self.prefix);
491 self.parent
492 .upsert_or_remove_at_pathbuf(rela_path, EditMode::Remove(RemoveMode::Any))?;
493 Ok(self)
494 }
495
496 pub fn remove_leaf<I, C>(&mut self, rela_path: I) -> Result<&mut Self>
498 where
499 I: IntoIterator<Item = C>,
500 C: AsRef<BStr>,
501 {
502 self.parent.path_buf.borrow_mut().clone_from(&self.prefix);
503 self.parent
504 .upsert_or_remove_at_pathbuf(rela_path, EditMode::Remove(RemoveMode::LeafOnly))?;
505 Ok(self)
506 }
507
508 pub fn write<E>(
510 &mut self,
511 out: impl FnMut(&Tree) -> std::result::Result<ObjectId, E>,
512 ) -> std::result::Result<ObjectId, E> {
513 self.parent.path_buf.borrow_mut().clone_from(&self.prefix);
514 self.parent.write_at_pathbuf(out, WriteMode::FromCursor)
515 }
516 }
517}
518
519#[derive(Copy, Clone, Eq, PartialEq)]
520enum UpsertMode {
521 Normal,
522 AssureTreeOnly,
524}
525
526#[derive(Copy, Clone, Eq, PartialEq)]
527enum RemoveMode {
528 Any,
530 LeafOnly,
532 IfLeaf,
534}
535
536#[derive(Copy, Clone)]
537enum EditMode {
538 Remove(RemoveMode),
539 Upsert(EntryKind, ObjectId, UpsertMode),
541}
542
543enum WriteMode {
544 Normal,
545 FromCursor,
547}
548
549fn cmp_entry_with_name(a: &tree::Entry, filename: &BStr, is_tree: bool) -> Ordering {
550 tree::name_order(&a.filename, a.mode.is_tree(), filename, is_tree)
551}
552
553fn filename(path: &BStr) -> &BStr {
554 path.rfind_byte(b'/').map_or(path, |pos| &path[pos + 1..])
555}
556
557fn empty_path() -> BString {
558 BString::default()
559}
560
561fn push_path_component(base: &mut BString, component: &[u8]) -> usize {
562 let prev_len = base.len();
563 debug_assert_ne!(base.last(), Some(&b'/'));
564 if !base.is_empty() {
565 base.push_byte(b'/');
566 }
567 base.push_str(component);
568 prev_len
569}
570
571fn path_with_component(base: &BStr, component: &BStr) -> BString {
572 if base.is_empty() {
573 component.into()
574 } else {
575 let mut out = base.to_owned();
576 out.push_byte(b'/');
577 out.push_str(component);
578 out
579 }
580}