@@ -4,6 +4,7 @@ use bstr::{BStr, BString, ByteSlice, ByteVec};
44use gix_hash:: ObjectId ;
55use gix_hashtable:: hash_map:: Entry ;
66use std:: cmp:: Ordering ;
7+ use std:: fmt:: Formatter ;
78
89/// A way to constrain all [tree-edits](Editor) to a given subtree.
910pub struct Cursor < ' a , ' find > {
@@ -14,6 +15,26 @@ pub struct Cursor<'a, 'find> {
1415 prefix : BString ,
1516}
1617
18+ impl std:: fmt:: Debug for Editor < ' _ > {
19+ fn fmt ( & self , f : & mut Formatter < ' _ > ) -> std:: fmt:: Result {
20+ f. debug_struct ( "Editor" )
21+ . field ( "object_hash" , & self . object_hash )
22+ . field ( "path_buf" , & self . path_buf )
23+ . field ( "trees" , & self . trees )
24+ . finish ( )
25+ }
26+ }
27+
28+ /// The error returned by [Editor] or [Cursor] edit operation.
29+ #[ derive( Debug , thiserror:: Error ) ]
30+ #[ allow( missing_docs) ]
31+ pub enum Error {
32+ #[ error( "Empty path components are not allowed" ) ]
33+ EmptyPathComponent ,
34+ #[ error( transparent) ]
35+ FindExistingObject ( #[ from] crate :: find:: existing_object:: Error ) ,
36+ }
37+
1738/// Lifecycle
1839impl < ' a > Editor < ' a > {
1940 /// Create a new editor that uses `root` as base for all edits. Use `find` to lookup existing
@@ -44,14 +65,22 @@ impl<'a> Editor<'a> {
4465 /// Future calls to [`upsert`](Self::upsert) or similar will keep working on the last seen state of the
4566 /// just-written root-tree.
4667 /// If this is not desired, use [set_root()](Self::set_root()).
68+ ///
69+ /// ### Validation
70+ ///
71+ /// Note that no additional validation is performed to assure correctness of entry-names.
72+ /// It is absolutely and intentionally possible to write out invalid trees with this method.
73+ /// Higher layers are expected to perform detailed validation.
4774 pub fn write < E > ( & mut self , out : impl FnMut ( & Tree ) -> Result < ObjectId , E > ) -> Result < ObjectId , E > {
4875 self . path_buf . clear ( ) ;
4976 self . write_at_pathbuf ( out, WriteMode :: Normal )
5077 }
5178
5279 /// Remove the entry at `rela_path`, loading all trees on the path accordingly.
5380 /// It's no error if the entry doesn't exist, or if `rela_path` doesn't lead to an existing entry at all.
54- pub fn remove < I , C > ( & mut self , rela_path : I ) -> Result < & mut Self , crate :: find:: existing_object:: Error >
81+ ///
82+ /// Note that trying to remove a path with an empty component is also forbidden.
83+ pub fn remove < I , C > ( & mut self , rela_path : I ) -> Result < & mut Self , Error >
5584 where
5685 I : IntoIterator < Item = C > ,
5786 C : AsRef < BStr > ,
@@ -74,12 +103,7 @@ impl<'a> Editor<'a> {
74103 ///
75104 /// `id` can also be an empty tree, along with [the respective `kind`](EntryKind::Tree), even though that's normally not allowed
76105 /// in Git trees.
77- pub fn upsert < I , C > (
78- & mut self ,
79- rela_path : I ,
80- kind : EntryKind ,
81- id : ObjectId ,
82- ) -> Result < & mut Self , crate :: find:: existing_object:: Error >
106+ pub fn upsert < I , C > ( & mut self , rela_path : I , kind : EntryKind , id : ObjectId ) -> Result < & mut Self , Error >
83107 where
84108 I : IntoIterator < Item = C > ,
85109 C : AsRef < BStr > ,
@@ -130,22 +154,39 @@ impl<'a> Editor<'a> {
130154 if tree. entries . is_empty ( ) {
131155 parent_to_adjust. entries . remove ( entry_idx) ;
132156 } else {
133- parent_to_adjust. entries [ entry_idx] . oid = out ( & tree) ?;
157+ match out ( & tree) {
158+ Ok ( id) => {
159+ parent_to_adjust. entries [ entry_idx] . oid = id;
160+ }
161+ Err ( err) => {
162+ let root_tree = parents. into_iter ( ) . next ( ) . expect ( "root wasn't consumed yet" ) ;
163+ self . trees . insert ( path_hash ( & root_tree. 1 ) , root_tree. 2 ) ;
164+ return Err ( err) ;
165+ }
166+ }
134167 }
135168 } else if parents. is_empty ( ) {
136169 debug_assert ! ( children. is_empty( ) , "we consume children before parents" ) ;
137170 debug_assert_eq ! ( rela_path, self . path_buf, "this should always be the root tree" ) ;
138171
139172 // There may be left-over trees if they are replaced with blobs for example.
140- let root_tree_id = out ( & tree) ?;
141- match mode {
142- WriteMode :: Normal => {
143- self . trees . clear ( ) ;
173+ match out ( & tree) {
174+ Ok ( id) => {
175+ let root_tree_id = id;
176+ match mode {
177+ WriteMode :: Normal => {
178+ self . trees . clear ( ) ;
179+ }
180+ WriteMode :: FromCursor => { }
181+ }
182+ self . trees . insert ( path_hash ( & rela_path) , tree) ;
183+ return Ok ( root_tree_id) ;
184+ }
185+ Err ( err) => {
186+ self . trees . insert ( path_hash ( & rela_path) , tree) ;
187+ return Err ( err) ;
144188 }
145- WriteMode :: FromCursor => { }
146189 }
147- self . trees . insert ( path_hash ( & self . path_buf ) , tree) ;
148- return Ok ( root_tree_id) ;
149190 } else if !tree. entries . is_empty ( ) {
150191 out ( & tree) ?;
151192 }
@@ -161,7 +202,7 @@ impl<'a> Editor<'a> {
161202 & mut self ,
162203 rela_path : I ,
163204 kind_and_id : Option < ( EntryKind , ObjectId , UpsertMode ) > ,
164- ) -> Result < & mut Self , crate :: find :: existing_object :: Error >
205+ ) -> Result < & mut Self , Error >
165206 where
166207 I : IntoIterator < Item = C > ,
167208 C : AsRef < BStr > ,
@@ -174,6 +215,9 @@ impl<'a> Editor<'a> {
174215 let new_kind_is_tree = kind_and_id. map_or ( false , |( kind, _, _) | kind == EntryKind :: Tree ) ;
175216 while let Some ( name) = rela_path. next ( ) {
176217 let name = name. as_ref ( ) ;
218+ if name. is_empty ( ) {
219+ return Err ( Error :: EmptyPathComponent ) ;
220+ }
177221 let is_last = rela_path. peek ( ) . is_none ( ) ;
178222 let mut needs_sorting = false ;
179223 let current_level_must_be_tree = !is_last || new_kind_is_tree;
@@ -301,7 +345,7 @@ mod cursor {
301345 ///
302346 /// The returned cursor will then allow applying edits to the tree at `rela_path` as root.
303347 /// If `rela_path` is a single empty string, it is equivalent to using the current instance itself.
304- pub fn cursor_at < I , C > ( & mut self , rela_path : I ) -> Result < Cursor < ' _ , ' a > , crate :: find :: existing_object :: Error >
348+ pub fn cursor_at < I , C > ( & mut self , rela_path : I ) -> Result < Cursor < ' _ , ' a > , super :: Error >
305349 where
306350 I : IntoIterator < Item = C > ,
307351 C : AsRef < BStr > ,
@@ -320,12 +364,7 @@ mod cursor {
320364
321365 impl < ' a , ' find > Cursor < ' a , ' find > {
322366 /// Like [`Editor::upsert()`], but with the constraint of only editing in this cursor's tree.
323- pub fn upsert < I , C > (
324- & mut self ,
325- rela_path : I ,
326- kind : EntryKind ,
327- id : ObjectId ,
328- ) -> Result < & mut Self , crate :: find:: existing_object:: Error >
367+ pub fn upsert < I , C > ( & mut self , rela_path : I , kind : EntryKind , id : ObjectId ) -> Result < & mut Self , super :: Error >
329368 where
330369 I : IntoIterator < Item = C > ,
331370 C : AsRef < BStr > ,
@@ -337,7 +376,7 @@ mod cursor {
337376 }
338377
339378 /// Like [`Editor::remove()`], but with the constraint of only editing in this cursor's tree.
340- pub fn remove < I , C > ( & mut self , rela_path : I ) -> Result < & mut Self , crate :: find :: existing_object :: Error >
379+ pub fn remove < I , C > ( & mut self , rela_path : I ) -> Result < & mut Self , super :: Error >
341380 where
342381 I : IntoIterator < Item = C > ,
343382 C : AsRef < BStr > ,
0 commit comments