1use std::{
2 cmp::Ordering,
3 collections::{HashMap, hash_map},
4 fmt::Formatter,
5};
6
7use bstr::{BStr, BString, ByteSlice, ByteVec};
8use gix_error::{ErrorExt, ExnResult, 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>(&mut self, out: impl FnMut(&Tree) -> Result<ObjectId, E>) -> Result<ObjectId, E> {
73 self.path_buf.borrow_mut().clear();
74 self.write_at_pathbuf(out, WriteMode::Normal)
75 }
76
77 pub fn remove<I, C>(&mut self, rela_path: I) -> ExnResult<&mut Self>
80 where
81 I: IntoIterator<Item = C>,
82 C: AsRef<BStr>,
83 {
84 self.path_buf.borrow_mut().clear();
85 self.upsert_or_remove_at_pathbuf(rela_path, EditMode::Remove(RemoveMode::Any))
86 }
87
88 pub fn remove_leaf<I, C>(&mut self, rela_path: I) -> ExnResult<&mut Self>
95 where
96 I: IntoIterator<Item = C>,
97 C: AsRef<BStr>,
98 {
99 self.path_buf.borrow_mut().clear();
100 self.upsert_or_remove_at_pathbuf(rela_path, EditMode::Remove(RemoveMode::LeafOnly))
101 }
102
103 pub fn get<I, C>(&self, rela_path: I) -> Option<&tree::Entry>
109 where
110 I: IntoIterator<Item = C>,
111 C: AsRef<BStr>,
112 {
113 self.path_buf.borrow_mut().clear();
114 self.get_inner(rela_path)
115 }
116
117 pub fn upsert<I, C>(&mut self, rela_path: I, kind: EntryKind, id: ObjectId) -> ExnResult<&mut Self>
132 where
133 I: IntoIterator<Item = C>,
134 C: AsRef<BStr>,
135 {
136 self.path_buf.borrow_mut().clear();
137 self.upsert_or_remove_at_pathbuf(rela_path, EditMode::Upsert(kind, id, UpsertMode::Normal))
138 }
139
140 fn get_inner<I, C>(&self, rela_path: I) -> Option<&tree::Entry>
141 where
142 I: IntoIterator<Item = C>,
143 C: AsRef<BStr>,
144 {
145 let mut path_buf = self.path_buf.borrow_mut();
146 let mut cursor = self.trees.get(path_buf.as_bstr()).expect("root is always present");
147 let mut rela_path = rela_path.into_iter().peekable();
148 while let Some(name) = rela_path.next() {
149 let name = name.as_ref();
150 let is_last = rela_path.peek().is_none();
151 match cursor
152 .entries
153 .binary_search_by(|e| cmp_entry_with_name(e, name, true))
154 .or_else(|_| cursor.entries.binary_search_by(|e| cmp_entry_with_name(e, name, false)))
155 {
156 Ok(idx) if is_last => return Some(&cursor.entries[idx]),
157 Ok(idx) => {
158 if cursor.entries[idx].mode.is_tree() {
159 push_path_component(&mut path_buf, name);
160 cursor = self.trees.get(path_buf.as_bstr())?;
161 } else {
162 break;
163 }
164 }
165 Err(_) => break,
166 }
167 }
168 None
169 }
170
171 fn write_at_pathbuf<E>(
172 &mut self,
173 mut out: impl FnMut(&Tree) -> Result<ObjectId, E>,
174 mode: WriteMode,
175 ) -> Result<ObjectId, E> {
176 assert_ne!(self.trees.len(), 0, "there is at least the root tree");
177
178 let path_buf = self.path_buf.borrow_mut();
180 let mut parents = vec![(
181 None::<usize>,
182 path_buf.clone(),
183 self.trees
184 .remove(path_buf.as_bstr())
185 .expect("root tree is always present"),
186 )];
187 let mut children = Vec::new();
188 while let Some((parent_idx, mut rela_path, mut tree)) = children.pop().or_else(|| parents.pop()) {
189 let mut all_entries_unchanged_or_written = true;
190 for entry in &tree.entries {
191 if entry.mode.is_tree() {
192 let prev_len = push_path_component(&mut rela_path, &entry.filename);
193 if let Some(sub_tree) = self.trees.remove(&rela_path) {
194 all_entries_unchanged_or_written = false;
195 let next_parent_idx = parents.len();
196 children.push((Some(next_parent_idx), rela_path.clone(), sub_tree));
197 }
198 rela_path.truncate(prev_len);
199 }
200 }
201 if all_entries_unchanged_or_written {
202 tree.entries.retain(|e| !e.oid.is_null());
203 if let Some((_, _, parent_to_adjust)) =
204 parent_idx.map(|idx| parents.get_mut(idx).expect("always present, pointing towards zero"))
205 {
206 let name = filename(rela_path.as_bstr());
207 let entry_idx = parent_to_adjust
208 .entries
209 .binary_search_by(|e| cmp_entry_with_name(e, name, true))
210 .expect("the parent always knows us by name");
211 if tree.entries.is_empty() {
212 parent_to_adjust.entries.remove(entry_idx);
213 } else {
214 match out(&tree) {
215 Ok(id) => {
216 parent_to_adjust.entries[entry_idx].oid = id;
217 }
218 Err(err) => {
219 let root_tree = parents.into_iter().next().expect("root wasn't consumed yet");
220 self.trees.insert(root_tree.1, root_tree.2);
221 return Err(err);
222 }
223 }
224 }
225 } else if parents.is_empty() {
226 debug_assert!(children.is_empty(), "we consume children before parents");
227 debug_assert_eq!(rela_path, **path_buf, "this should always be the root tree");
228
229 match out(&tree) {
231 Ok(id) => {
232 let root_tree_id = id;
233 match mode {
234 WriteMode::Normal => {
235 self.trees.clear();
236 }
237 WriteMode::FromCursor => {}
238 }
239 self.trees.insert(rela_path, tree);
240 return Ok(root_tree_id);
241 }
242 Err(err) => {
243 self.trees.insert(rela_path, tree);
244 return Err(err);
245 }
246 }
247 } else if !tree.entries.is_empty() {
248 out(&tree)?;
249 }
250 } else {
251 parents.push((parent_idx, rela_path, tree));
252 }
253 }
254
255 unreachable!("we exit as soon as everything is consumed")
256 }
257
258 fn upsert_or_remove_at_pathbuf<I, C>(&mut self, rela_path: I, edit: EditMode) -> ExnResult<&mut Self>
259 where
260 I: IntoIterator<Item = C>,
261 C: AsRef<BStr>,
262 {
263 let mut path_buf = self.path_buf.borrow_mut();
264 let mut cursor = self.trees.get_mut(path_buf.as_bstr()).expect("root is always present");
265 let mut rela_path = rela_path.into_iter().peekable();
266 let new_kind_is_tree = matches!(edit, EditMode::Upsert(EntryKind::Tree, _, _));
267 while let Some(name) = rela_path.next() {
268 let name = name.as_ref();
269 if name.is_empty() {
270 return Err(validation("Empty path components are not allowed").raise_erased());
271 }
272 let is_last = rela_path.peek().is_none();
273 let mut needs_sorting = false;
274 let current_level_must_be_tree = !is_last || new_kind_is_tree;
275 let check_type_change = |entry: &tree::Entry| entry.mode.is_tree() != current_level_must_be_tree;
276 let tree_to_lookup = match cursor
277 .entries
278 .binary_search_by(|e| cmp_entry_with_name(e, name, false))
279 .or_else(|file_insertion_idx| {
280 cursor
281 .entries
282 .binary_search_by(|e| cmp_entry_with_name(e, name, true))
283 .map_err(|dir_insertion_index| {
284 if current_level_must_be_tree {
285 dir_insertion_index
286 } else {
287 file_insertion_idx
288 }
289 })
290 }) {
291 Ok(idx) => {
292 match edit {
293 EditMode::Remove(mode) => {
294 if is_last {
295 if mode == RemoveMode::LeafOnly && cursor.entries[idx].mode.is_tree() {
296 let rela_path = path_with_component(path_buf.as_bstr(), name);
297 return Err(validation(format!(
298 "Cannot remove '{rela_path}' as leaf entry because it is a tree"
299 ))
300 .raise_erased());
301 }
302 cursor.entries.remove(idx);
303 break;
304 } else {
305 let entry = &cursor.entries[idx];
306 if entry.mode.is_tree() {
307 Some(entry.oid)
308 } else {
309 break;
310 }
311 }
312 }
313 EditMode::Upsert(kind, id, _mode) => {
314 let entry = &mut cursor.entries[idx];
315 if is_last {
316 entry.oid = id;
318 needs_sorting = check_type_change(entry);
319 entry.mode = kind.into();
320 None
321 } else if entry.mode.is_tree() {
322 Some(entry.oid)
324 } else {
325 entry.oid = id.kind().null();
327 needs_sorting = check_type_change(entry);
328 entry.mode = EntryKind::Tree.into();
329 None
330 }
331 }
332 }
333 }
334 Err(insertion_idx) => match edit {
335 EditMode::Remove(_) => break,
336 EditMode::Upsert(kind, id, _mode) => {
337 cursor.entries.insert(
338 insertion_idx,
339 tree::Entry {
340 filename: name.into(),
341 mode: if is_last { kind.into() } else { EntryKind::Tree.into() },
342 oid: if is_last { id } else { id.kind().null() },
343 },
344 );
345 None
346 }
347 },
348 };
349 if needs_sorting {
350 cursor.entries.sort();
351 }
352 if is_last && matches!(edit, EditMode::Upsert(_, _, UpsertMode::Normal)) {
353 break;
354 }
355 let stop_at_unedited_empty_tree = matches!(edit, EditMode::Remove(RemoveMode::LeafOnly))
356 && tree_to_lookup.is_some_and(|id| id.is_empty_tree());
357 push_path_component(&mut path_buf, name);
358 cursor = match self.trees.entry(path_buf.clone()) {
359 hash_map::Entry::Occupied(e) => e.into_mut(),
360 hash_map::Entry::Vacant(_) if stop_at_unedited_empty_tree => break,
361 hash_map::Entry::Vacant(e) => e.insert(
362 if let Some(tree_id) = tree_to_lookup.filter(|tree_id| !tree_id.is_empty_tree()) {
363 self.find.find_tree(&tree_id, &mut self.tree_buf)?.into()
364 } else {
365 Tree::default()
366 },
367 ),
368 };
369 }
370 drop(path_buf);
371 Ok(self)
372 }
373
374 pub fn set_root(&mut self, root: Tree) -> &mut Self {
380 self.trees.clear();
381 self.trees.insert(empty_path(), root);
382 self
383 }
384}
385
386mod cursor {
387 use bstr::{BStr, BString};
388 use gix_error::ExnResult;
389 use gix_hash::ObjectId;
390
391 use crate::{
392 Tree, tree,
393 tree::{
394 Editor, EntryKind,
395 editor::{Cursor, EditMode, RemoveMode, UpsertMode, WriteMode},
396 },
397 };
398
399 impl<'a> Editor<'a> {
401 pub fn to_cursor(&mut self) -> Cursor<'_, 'a> {
405 Cursor {
406 parent: self,
407 prefix: BString::default(),
408 }
409 }
410
411 pub fn cursor_at<I, C>(&mut self, rela_path: I) -> ExnResult<Cursor<'_, 'a>>
416 where
417 I: IntoIterator<Item = C>,
418 C: AsRef<BStr>,
419 {
420 self.path_buf.borrow_mut().clear();
421 self.upsert_or_remove_at_pathbuf(
422 rela_path,
423 EditMode::Upsert(EntryKind::Tree, self.object_hash.null(), UpsertMode::AssureTreeOnly),
424 )?;
425 let prefix = self.path_buf.borrow_mut().clone();
426 Ok(Cursor {
427 prefix, parent: self,
429 })
430 }
431 }
432
433 impl Cursor<'_, '_> {
434 pub fn get<I, C>(&self, rela_path: I) -> Option<&tree::Entry>
440 where
441 I: IntoIterator<Item = C>,
442 C: AsRef<BStr>,
443 {
444 self.parent.path_buf.borrow_mut().clone_from(&self.prefix);
445 self.parent.get_inner(rela_path)
446 }
447
448 pub fn upsert<I, C>(&mut self, rela_path: I, kind: EntryKind, id: ObjectId) -> ExnResult<&mut Self>
450 where
451 I: IntoIterator<Item = C>,
452 C: AsRef<BStr>,
453 {
454 self.parent.path_buf.borrow_mut().clone_from(&self.prefix);
455 self.parent
456 .upsert_or_remove_at_pathbuf(rela_path, EditMode::Upsert(kind, id, UpsertMode::Normal))?;
457 Ok(self)
458 }
459
460 pub fn remove<I, C>(&mut self, rela_path: I) -> ExnResult<&mut Self>
462 where
463 I: IntoIterator<Item = C>,
464 C: AsRef<BStr>,
465 {
466 self.parent.path_buf.borrow_mut().clone_from(&self.prefix);
467 self.parent
468 .upsert_or_remove_at_pathbuf(rela_path, EditMode::Remove(RemoveMode::Any))?;
469 Ok(self)
470 }
471
472 pub fn remove_leaf<I, C>(&mut self, rela_path: I) -> ExnResult<&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::Remove(RemoveMode::LeafOnly))?;
481 Ok(self)
482 }
483
484 pub fn write<E>(&mut self, out: impl FnMut(&Tree) -> Result<ObjectId, E>) -> Result<ObjectId, E> {
486 self.parent.path_buf.borrow_mut().clone_from(&self.prefix);
487 self.parent.write_at_pathbuf(out, WriteMode::FromCursor)
488 }
489 }
490}
491
492#[derive(Copy, Clone, Eq, PartialEq)]
493enum UpsertMode {
494 Normal,
495 AssureTreeOnly,
497}
498
499#[derive(Copy, Clone, Eq, PartialEq)]
500enum RemoveMode {
501 Any,
503 LeafOnly,
505}
506
507#[derive(Copy, Clone)]
508enum EditMode {
509 Remove(RemoveMode),
510 Upsert(EntryKind, ObjectId, UpsertMode),
512}
513
514enum WriteMode {
515 Normal,
516 FromCursor,
518}
519
520fn cmp_entry_with_name(a: &tree::Entry, filename: &BStr, is_tree: bool) -> Ordering {
521 tree::name_order(&a.filename, a.mode.is_tree(), filename, is_tree)
522}
523
524fn filename(path: &BStr) -> &BStr {
525 path.rfind_byte(b'/').map_or(path, |pos| &path[pos + 1..])
526}
527
528fn empty_path() -> BString {
529 BString::default()
530}
531
532fn push_path_component(base: &mut BString, component: &[u8]) -> usize {
533 let prev_len = base.len();
534 debug_assert_ne!(base.last(), Some(&b'/'));
535 if !base.is_empty() {
536 base.push_byte(b'/');
537 }
538 base.push_str(component);
539 prev_len
540}
541
542fn path_with_component(base: &BStr, component: &BStr) -> BString {
543 if base.is_empty() {
544 component.into()
545 } else {
546 let mut out = base.to_owned();
547 out.push_byte(b'/');
548 out.push_str(component);
549 out
550 }
551}