@@ -4,6 +4,7 @@ use bstr::{BStr, BString, ByteSlice, ByteVec};
4
4
use gix_hash:: ObjectId ;
5
5
use gix_hashtable:: hash_map:: Entry ;
6
6
use std:: cmp:: Ordering ;
7
+ use std:: fmt:: Formatter ;
7
8
8
9
/// A way to constrain all [tree-edits](Editor) to a given subtree.
9
10
pub struct Cursor < ' a , ' find > {
@@ -14,6 +15,26 @@ pub struct Cursor<'a, 'find> {
14
15
prefix : BString ,
15
16
}
16
17
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
+
17
38
/// Lifecycle
18
39
impl < ' a > Editor < ' a > {
19
40
/// 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> {
44
65
/// Future calls to [`upsert`](Self::upsert) or similar will keep working on the last seen state of the
45
66
/// just-written root-tree.
46
67
/// 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.
47
74
pub fn write < E > ( & mut self , out : impl FnMut ( & Tree ) -> Result < ObjectId , E > ) -> Result < ObjectId , E > {
48
75
self . path_buf . clear ( ) ;
49
76
self . write_at_pathbuf ( out, WriteMode :: Normal )
50
77
}
51
78
52
79
/// Remove the entry at `rela_path`, loading all trees on the path accordingly.
53
80
/// 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 >
55
84
where
56
85
I : IntoIterator < Item = C > ,
57
86
C : AsRef < BStr > ,
@@ -74,12 +103,7 @@ impl<'a> Editor<'a> {
74
103
///
75
104
/// `id` can also be an empty tree, along with [the respective `kind`](EntryKind::Tree), even though that's normally not allowed
76
105
/// 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 >
83
107
where
84
108
I : IntoIterator < Item = C > ,
85
109
C : AsRef < BStr > ,
@@ -130,22 +154,39 @@ impl<'a> Editor<'a> {
130
154
if tree. entries . is_empty ( ) {
131
155
parent_to_adjust. entries . remove ( entry_idx) ;
132
156
} 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
+ }
134
167
}
135
168
} else if parents. is_empty ( ) {
136
169
debug_assert ! ( children. is_empty( ) , "we consume children before parents" ) ;
137
170
debug_assert_eq ! ( rela_path, self . path_buf, "this should always be the root tree" ) ;
138
171
139
172
// 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) ;
144
188
}
145
- WriteMode :: FromCursor => { }
146
189
}
147
- self . trees . insert ( path_hash ( & self . path_buf ) , tree) ;
148
- return Ok ( root_tree_id) ;
149
190
} else if !tree. entries . is_empty ( ) {
150
191
out ( & tree) ?;
151
192
}
@@ -161,7 +202,7 @@ impl<'a> Editor<'a> {
161
202
& mut self ,
162
203
rela_path : I ,
163
204
kind_and_id : Option < ( EntryKind , ObjectId , UpsertMode ) > ,
164
- ) -> Result < & mut Self , crate :: find :: existing_object :: Error >
205
+ ) -> Result < & mut Self , Error >
165
206
where
166
207
I : IntoIterator < Item = C > ,
167
208
C : AsRef < BStr > ,
@@ -174,6 +215,9 @@ impl<'a> Editor<'a> {
174
215
let new_kind_is_tree = kind_and_id. map_or ( false , |( kind, _, _) | kind == EntryKind :: Tree ) ;
175
216
while let Some ( name) = rela_path. next ( ) {
176
217
let name = name. as_ref ( ) ;
218
+ if name. is_empty ( ) {
219
+ return Err ( Error :: EmptyPathComponent ) ;
220
+ }
177
221
let is_last = rela_path. peek ( ) . is_none ( ) ;
178
222
let mut needs_sorting = false ;
179
223
let current_level_must_be_tree = !is_last || new_kind_is_tree;
@@ -301,7 +345,7 @@ mod cursor {
301
345
///
302
346
/// The returned cursor will then allow applying edits to the tree at `rela_path` as root.
303
347
/// 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 >
305
349
where
306
350
I : IntoIterator < Item = C > ,
307
351
C : AsRef < BStr > ,
@@ -320,12 +364,7 @@ mod cursor {
320
364
321
365
impl < ' a , ' find > Cursor < ' a , ' find > {
322
366
/// 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 >
329
368
where
330
369
I : IntoIterator < Item = C > ,
331
370
C : AsRef < BStr > ,
@@ -337,7 +376,7 @@ mod cursor {
337
376
}
338
377
339
378
/// 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 >
341
380
where
342
381
I : IntoIterator < Item = C > ,
343
382
C : AsRef < BStr > ,
0 commit comments