|
| 1 | +use crate::bstr::{BStr, BString}; |
| 2 | +use crate::prelude::ObjectIdExt; |
| 3 | +use crate::{Id, Repository}; |
| 4 | +use gix_hash::ObjectId; |
| 5 | +use gix_object::tree::EntryKind; |
| 6 | + |
| 7 | +/// |
| 8 | +pub mod init { |
| 9 | + /// The error returned by [`Editor::new()](crate::object::tree::Editor::new()). |
| 10 | + #[derive(Debug, thiserror::Error)] |
| 11 | + #[allow(missing_docs)] |
| 12 | + pub enum Error { |
| 13 | + #[error(transparent)] |
| 14 | + DecodeTree(#[from] gix_object::decode::Error), |
| 15 | + } |
| 16 | +} |
| 17 | + |
| 18 | +/// |
| 19 | +pub mod write { |
| 20 | + /// The error returned by [`Editor::write()](crate::object::tree::Editor::write()) and [`Cursor::write()](super::Cursor::write). |
| 21 | + #[derive(Debug, thiserror::Error)] |
| 22 | + #[allow(missing_docs)] |
| 23 | + pub enum Error { |
| 24 | + #[error(transparent)] |
| 25 | + WriteTree(#[from] crate::object::write::Error), |
| 26 | + } |
| 27 | +} |
| 28 | + |
| 29 | +/// A cursor at a specific portion of a tree to [edit](super::Editor). |
| 30 | +pub struct Cursor<'a, 'repo> { |
| 31 | + inner: gix_object::tree::editor::Cursor<'a, 'repo>, |
| 32 | + repo: &'repo Repository, |
| 33 | +} |
| 34 | + |
| 35 | +/// Lifecycle |
| 36 | +impl<'repo> super::Editor<'repo> { |
| 37 | + /// Initialize a new editor from the given `tree`. |
| 38 | + pub fn new(tree: &crate::Tree<'repo>) -> Result<Self, init::Error> { |
| 39 | + let tree_ref = tree.decode()?; |
| 40 | + let repo = tree.repo; |
| 41 | + Ok(super::Editor { |
| 42 | + inner: gix_object::tree::Editor::new(tree_ref.into(), &repo.objects, repo.object_hash()), |
| 43 | + repo, |
| 44 | + }) |
| 45 | + } |
| 46 | +} |
| 47 | + |
| 48 | +/// Tree editing |
| 49 | +#[cfg(feature = "tree-editor")] |
| 50 | +impl<'repo> crate::Tree<'repo> { |
| 51 | + /// Start editing a new tree based on this one. |
| 52 | + #[doc(alias = "treebuilder", alias = "git2")] |
| 53 | + pub fn edit(&self) -> Result<super::Editor<'repo>, init::Error> { |
| 54 | + super::Editor::new(self) |
| 55 | + } |
| 56 | +} |
| 57 | + |
| 58 | +/// Obtain an iterator over `BStr`-components. |
| 59 | +/// |
| 60 | +/// Note that the implementation is simple, and it's mainly meant for statically known strings |
| 61 | +/// or locations obtained during a merge. |
| 62 | +pub trait ToComponentIterator { |
| 63 | + /// Return an iterator over the components of a path, without the separator. |
| 64 | + fn to_component_iterator(&self) -> impl Iterator<Item = &BStr>; |
| 65 | +} |
| 66 | + |
| 67 | +impl ToComponentIterator for &str { |
| 68 | + fn to_component_iterator(&self) -> impl Iterator<Item = &BStr> { |
| 69 | + self.split('/').map(Into::into) |
| 70 | + } |
| 71 | +} |
| 72 | + |
| 73 | +impl ToComponentIterator for String { |
| 74 | + fn to_component_iterator(&self) -> impl Iterator<Item = &BStr> { |
| 75 | + self.split('/').map(Into::into) |
| 76 | + } |
| 77 | +} |
| 78 | + |
| 79 | +impl ToComponentIterator for &String { |
| 80 | + fn to_component_iterator(&self) -> impl Iterator<Item = &BStr> { |
| 81 | + self.split('/').map(Into::into) |
| 82 | + } |
| 83 | +} |
| 84 | + |
| 85 | +impl ToComponentIterator for BString { |
| 86 | + fn to_component_iterator(&self) -> impl Iterator<Item = &BStr> { |
| 87 | + self.split(|b| *b == b'/').map(Into::into) |
| 88 | + } |
| 89 | +} |
| 90 | + |
| 91 | +impl ToComponentIterator for &BString { |
| 92 | + fn to_component_iterator(&self) -> impl Iterator<Item = &BStr> { |
| 93 | + self.split(|b| *b == b'/').map(Into::into) |
| 94 | + } |
| 95 | +} |
| 96 | + |
| 97 | +impl ToComponentIterator for &BStr { |
| 98 | + fn to_component_iterator(&self) -> impl Iterator<Item = &BStr> { |
| 99 | + self.split(|b| *b == b'/').map(Into::into) |
| 100 | + } |
| 101 | +} |
| 102 | + |
| 103 | +/// Cursor Handling |
| 104 | +impl<'repo> super::Editor<'repo> { |
| 105 | + /// Turn ourselves as a cursor, which points to the same tree as the editor. |
| 106 | + /// |
| 107 | + /// This is useful if a method takes a [`Cursor`], not an [`Editor`](super::Editor). |
| 108 | + pub fn to_cursor(&mut self) -> Cursor<'_, 'repo> { |
| 109 | + Cursor { |
| 110 | + inner: self.inner.to_cursor(), |
| 111 | + repo: self.repo, |
| 112 | + } |
| 113 | + } |
| 114 | + |
| 115 | + /// Create a cursor at the given `rela_path`, which must be a tree or is turned into a tree as its own edit. |
| 116 | + /// |
| 117 | + /// The returned cursor will then allow applying edits to the tree at `rela_path` as root. |
| 118 | + /// If `rela_path` is a single empty string, it is equivalent to using the current instance itself. |
| 119 | + pub fn cursor_at( |
| 120 | + &mut self, |
| 121 | + rela_path: impl ToComponentIterator, |
| 122 | + ) -> Result<Cursor<'_, 'repo>, gix_object::find::existing_object::Error> { |
| 123 | + Ok(Cursor { |
| 124 | + inner: self.inner.cursor_at(rela_path.to_component_iterator())?, |
| 125 | + repo: self.repo, |
| 126 | + }) |
| 127 | + } |
| 128 | +} |
| 129 | +/// Operations |
| 130 | +impl<'repo> Cursor<'_, 'repo> { |
| 131 | + /// Like [`Editor::upsert()`](super::Editor::upsert()), but with the constraint of only editing in this cursor's tree. |
| 132 | + pub fn upsert( |
| 133 | + &mut self, |
| 134 | + rela_path: impl ToComponentIterator, |
| 135 | + kind: EntryKind, |
| 136 | + id: impl Into<ObjectId>, |
| 137 | + ) -> Result<&mut Self, gix_object::find::existing_object::Error> { |
| 138 | + self.inner.upsert(rela_path.to_component_iterator(), kind, id.into())?; |
| 139 | + Ok(self) |
| 140 | + } |
| 141 | + |
| 142 | + /// Like [`Editor::remove()`](super::Editor::remove), but with the constraint of only editing in this cursor's tree. |
| 143 | + pub fn remove( |
| 144 | + &mut self, |
| 145 | + rela_path: impl ToComponentIterator, |
| 146 | + ) -> Result<&mut Self, gix_object::find::existing_object::Error> { |
| 147 | + self.inner.remove(rela_path.to_component_iterator())?; |
| 148 | + Ok(self) |
| 149 | + } |
| 150 | + |
| 151 | + /// Like [`Editor::write()`](super::Editor::write()), but will write only the subtree of the cursor. |
| 152 | + pub fn write(&mut self) -> Result<Id<'repo>, write::Error> { |
| 153 | + write_cursor(self) |
| 154 | + } |
| 155 | +} |
| 156 | + |
| 157 | +/// Operations |
| 158 | +impl<'repo> super::Editor<'repo> { |
| 159 | + /// Insert a new entry of `kind` with `id` at `rela_path`, an iterator over each path component in the tree, |
| 160 | + /// like `a/b/c`. Names are matched case-sensitively. |
| 161 | + /// |
| 162 | + /// Existing leaf-entries will be overwritten unconditionally, and it is assumed that `id` is available in the object database |
| 163 | + /// or will be made available at a later point to assure the integrity of the produced tree. |
| 164 | + /// |
| 165 | + /// Intermediate trees will be created if they don't exist in the object database, otherwise they will be loaded and entries |
| 166 | + /// will be inserted into them instead. |
| 167 | + /// |
| 168 | + /// Note that `id` can be [null](ObjectId::null()) to create a placeholder. These will not be written, and paths leading |
| 169 | + /// through them will not be considered a problem. |
| 170 | + /// |
| 171 | + /// `id` can also be an empty tree, along with [the respective `kind`](EntryKind::Tree), even though that's normally not allowed |
| 172 | + /// in Git trees. |
| 173 | + /// |
| 174 | + /// Validation of path-components will not be performed here, but when [writing the tree](Self::write()). |
| 175 | + pub fn upsert( |
| 176 | + &mut self, |
| 177 | + rela_path: impl ToComponentIterator, |
| 178 | + kind: EntryKind, |
| 179 | + id: impl Into<ObjectId>, |
| 180 | + ) -> Result<&mut Self, gix_object::find::existing_object::Error> { |
| 181 | + self.inner.upsert(rela_path.to_component_iterator(), kind, id.into())?; |
| 182 | + Ok(self) |
| 183 | + } |
| 184 | + |
| 185 | + /// Remove the entry at `rela_path`, loading all trees on the path accordingly. |
| 186 | + /// It's no error if the entry doesn't exist, or if `rela_path` doesn't lead to an existing entry at all. |
| 187 | + pub fn remove( |
| 188 | + &mut self, |
| 189 | + rela_path: impl ToComponentIterator, |
| 190 | + ) -> Result<&mut Self, gix_object::find::existing_object::Error> { |
| 191 | + self.inner.remove(rela_path.to_component_iterator())?; |
| 192 | + Ok(self) |
| 193 | + } |
| 194 | + |
| 195 | + /// Write the entire in-memory state of all changed trees (and only changed trees) to the object database. |
| 196 | + /// Note that the returned object id *can* be the empty tree if everything was removed or if nothing |
| 197 | + /// was added to the tree. |
| 198 | + /// |
| 199 | + /// The last call to `out` will be the changed root tree, whose object-id will also be returned. |
| 200 | + /// `out` is free to do any kind of additional validation, like to assure that all entries in the tree exist. |
| 201 | + /// We don't assure that as there is no validation that inserted entries are valid object ids. |
| 202 | + /// |
| 203 | + /// Future calls to [`upsert`](Self::upsert) or similar will keep working on the last seen state of the |
| 204 | + /// just-written root-tree. |
| 205 | + /// If this is not desired, use [set_root()](Self::set_root()). |
| 206 | + /// |
| 207 | + /// Before writing a tree, all of its entries (not only added ones), will be validated to assure they are |
| 208 | + /// correct. |
| 209 | + pub fn write(&mut self) -> Result<Id<'repo>, write::Error> { |
| 210 | + write_cursor(&mut self.to_cursor()) |
| 211 | + } |
| 212 | +} |
| 213 | + |
| 214 | +fn write_cursor<'repo>(cursor: &mut Cursor<'_, 'repo>) -> Result<Id<'repo>, write::Error> { |
| 215 | + cursor |
| 216 | + .inner |
| 217 | + .write(|tree| -> Result<ObjectId, write::Error> { Ok(cursor.repo.write_object(tree)?.detach()) }) |
| 218 | + .map(|id| id.attach(cursor.repo)) |
| 219 | +} |
0 commit comments