1
- use crate :: tree:: EntryKind ;
1
+ use crate :: tree:: { Editor , EntryKind } ;
2
2
use crate :: { tree, Tree } ;
3
3
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
7
8
- /// The state needed to apply edits instantly to in-memory trees.
9
- ///
10
- /// It's made so that each tree is looked at in the object database at most once, and held in memory for
11
- /// all edits until everything is flushed to write all changed trees.
12
- ///
13
- /// The editor is optimized to edit existing trees, but can deal with building entirely new trees as well
14
- /// with some penalties.
15
- ///
16
- /// ### Note
17
- ///
18
- /// For reasons of efficiency, internally a SHA1 based hashmap is used to avoid having to store full paths
19
- /// to each edited tree. The chance of collision is low, but could be engineered to overwrite or write into
20
- /// an unintended tree.
21
- #[ doc( alias = "TreeUpdateBuilder" , alias = "git2" ) ]
22
- pub struct Editor < ' a > {
23
- /// A way to lookup trees.
24
- find : & ' a dyn crate :: FindExt ,
25
- /// All trees we currently hold in memory. Each of these may change while adding and removing entries.
26
- /// null-object-ids mark tree-entries whose value we don't know yet, they are placeholders that will be
27
- /// dropped when writing at the latest.
28
- trees : gix_hashtable:: HashMap < ObjectId , Tree > ,
29
- /// A buffer to build up paths when finding the tree to edit.
30
- path_buf : BString ,
31
- /// Our buffer for storing tree-data in, right before decoding it.
32
- tree_buf : Vec < u8 > ,
8
+ /// A way to constrain all [tree-edits](Editor) to a given subtree.
9
+ pub struct Cursor < ' a , ' find > {
10
+ /// The underlying editor
11
+ parent : & ' a mut Editor < ' find > ,
12
+ /// Our own location, used as prefix for all operations.
13
+ /// Note that it's assumed to always contain a tree.
14
+ prefix : BString ,
33
15
}
34
16
35
17
/// Lifecycle
36
18
impl < ' a > Editor < ' a > {
37
19
/// Create a new editor that uses `root` as base for all edits. Use `find` to lookup existing
38
20
/// trees when edits are made. Each tree will only be looked-up once and then edited in place from
39
21
/// that point on.
40
- pub fn new ( root : Tree , find : & ' a dyn crate :: FindExt ) -> Self {
22
+ /// `object_hash` denotes the kind of hash to create.
23
+ pub fn new ( root : Tree , find : & ' a dyn crate :: FindExt , object_hash : gix_hash:: Kind ) -> Self {
41
24
Editor {
42
25
find,
26
+ object_hash,
43
27
trees : gix_hashtable:: HashMap :: from_iter ( Some ( ( empty_path_hash ( ) , root) ) ) ,
44
28
path_buf : Vec :: with_capacity ( 256 ) . into ( ) ,
45
29
tree_buf : Vec :: with_capacity ( 512 ) ,
@@ -60,15 +44,63 @@ impl<'a> Editor<'a> {
60
44
/// Future calls to [`upsert`](Self::upsert) or similar will keep working on the last seen state of the
61
45
/// just-written root-tree.
62
46
/// If this is not desired, use [set_root()](Self::set_root()).
63
- pub fn write < E > ( & mut self , mut out : impl FnMut ( & Tree ) -> Result < ObjectId , E > ) -> Result < ObjectId , E > {
47
+ pub fn write < E > ( & mut self , out : impl FnMut ( & Tree ) -> Result < ObjectId , E > ) -> Result < ObjectId , E > {
48
+ self . path_buf . clear ( ) ;
49
+ self . write_at_pathbuf ( out, WriteMode :: Normal )
50
+ }
51
+
52
+ /// Remove the entry at `rela_path`, loading all trees on the path accordingly.
53
+ /// 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 >
55
+ where
56
+ I : IntoIterator < Item = C > ,
57
+ C : AsRef < BStr > ,
58
+ {
59
+ self . path_buf . clear ( ) ;
60
+ self . upsert_or_remove_at_pathbuf ( rela_path, None )
61
+ }
62
+
63
+ /// Insert a new entry of `kind` with `id` at `rela_path`, an iterator over each path component in the tree,
64
+ /// like `a/b/c`. Names are matched case-sensitively.
65
+ ///
66
+ /// Existing leaf-entries will be overwritten unconditionally, and it is assumed that `id` is available in the object database
67
+ /// or will be made available at a later point to assure the integrity of the produced tree.
68
+ ///
69
+ /// Intermediate trees will be created if they don't exist in the object database, otherwise they will be loaded and entries
70
+ /// will be inserted into them instead.
71
+ ///
72
+ /// Note that `id` can be [null](ObjectId::null()) to create a placeholder. These will not be written, and paths leading
73
+ /// through them will not be considered a problem.
74
+ ///
75
+ /// `id` can also be an empty tree, along with [the respective `kind`](EntryKind::Tree), even though that's normally not allowed
76
+ /// 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 >
83
+ where
84
+ I : IntoIterator < Item = C > ,
85
+ C : AsRef < BStr > ,
86
+ {
87
+ self . path_buf . clear ( ) ;
88
+ self . upsert_or_remove_at_pathbuf ( rela_path, Some ( ( kind, id, UpsertMode :: Normal ) ) )
89
+ }
90
+
91
+ fn write_at_pathbuf < E > (
92
+ & mut self ,
93
+ mut out : impl FnMut ( & Tree ) -> Result < ObjectId , E > ,
94
+ mode : WriteMode ,
95
+ ) -> Result < ObjectId , E > {
64
96
assert_ne ! ( self . trees. len( ) , 0 , "there is at least the root tree" ) ;
65
97
66
98
// back is for children, front is for parents.
67
99
let mut parents = vec ! [ (
68
100
None :: <usize >,
69
101
BString :: default ( ) ,
70
102
self . trees
71
- . remove( & empty_path_hash ( ) )
103
+ . remove( & path_hash ( & self . path_buf ) )
72
104
. expect( "root tree is always present" ) ,
73
105
) ] ;
74
106
let mut children = Vec :: new ( ) ;
@@ -106,8 +138,13 @@ impl<'a> Editor<'a> {
106
138
107
139
// There may be left-over trees if they are replaced with blobs for example.
108
140
let root_tree_id = out ( & tree) ?;
109
- self . trees . clear ( ) ;
110
- self . trees . insert ( empty_path_hash ( ) , tree) ;
141
+ match mode {
142
+ WriteMode :: Normal => {
143
+ self . trees . clear ( ) ;
144
+ }
145
+ WriteMode :: FromCursor => { }
146
+ }
147
+ self . trees . insert ( path_hash ( & self . path_buf ) , tree) ;
111
148
return Ok ( root_tree_id) ;
112
149
} else if !tree. entries . is_empty ( ) {
113
150
out ( & tree) ?;
@@ -120,56 +157,21 @@ impl<'a> Editor<'a> {
120
157
unreachable ! ( "we exit as soon as everything is consumed" )
121
158
}
122
159
123
- /// Remove the entry at `rela_path`, loading all trees on the path accordingly.
124
- /// It's no error if the entry doesn't exist, or if `rela_path` doesn't lead to an existing entry at all.
125
- pub fn remove < I , C > ( & mut self , rela_path : I ) -> Result < & mut Self , crate :: find:: existing_object:: Error >
126
- where
127
- I : IntoIterator < Item = C > ,
128
- C : AsRef < BStr > ,
129
- {
130
- self . upsert_or_remove ( rela_path, None )
131
- }
132
-
133
- /// Insert a new entry of `kind` with `id` at `rela_path`, an iterator over each path component in the tree,
134
- /// like `a/b/c`. Names are matched case-sensitively.
135
- ///
136
- /// Existing leaf-entries will be overwritten unconditionally, and it is assumed that `id` is available in the object database
137
- /// or will be made available at a later point to assure the integrity of the produced tree.
138
- ///
139
- /// Intermediate trees will be created if they don't exist in the object database, otherwise they will be loaded and entries
140
- /// will be inserted into them instead.
141
- ///
142
- /// Note that `id` can be [null](ObjectId::null()) to create a placeholder. These will not be written, and paths leading
143
- /// through them will not be considered a problem.
144
- ///
145
- /// `id` can also be an empty tree, along with [the respective `kind`](EntryKind::Tree), even though that's normally not allowed
146
- /// in Git trees.
147
- pub fn upsert < I , C > (
160
+ fn upsert_or_remove_at_pathbuf < I , C > (
148
161
& mut self ,
149
162
rela_path : I ,
150
- kind : EntryKind ,
151
- id : ObjectId ,
163
+ kind_and_id : Option < ( EntryKind , ObjectId , UpsertMode ) > ,
152
164
) -> Result < & mut Self , crate :: find:: existing_object:: Error >
153
165
where
154
166
I : IntoIterator < Item = C > ,
155
167
C : AsRef < BStr > ,
156
168
{
157
- self . upsert_or_remove ( rela_path, Some ( ( kind, id) ) )
158
- }
159
-
160
- fn upsert_or_remove < I , C > (
161
- & mut self ,
162
- rela_path : I ,
163
- kind_and_id : Option < ( EntryKind , ObjectId ) > ,
164
- ) -> Result < & mut Self , crate :: find:: existing_object:: Error >
165
- where
166
- I : IntoIterator < Item = C > ,
167
- C : AsRef < BStr > ,
168
- {
169
- let mut cursor = self . trees . get_mut ( & empty_path_hash ( ) ) . expect ( "root is always present" ) ;
170
- self . path_buf . clear ( ) ;
169
+ let mut cursor = self
170
+ . trees
171
+ . get_mut ( & path_hash ( & self . path_buf ) )
172
+ . expect ( "root is always present" ) ;
171
173
let mut rela_path = rela_path. into_iter ( ) . peekable ( ) ;
172
- let new_kind_is_tree = kind_and_id. map_or ( false , |( kind, _) | kind == EntryKind :: Tree ) ;
174
+ let new_kind_is_tree = kind_and_id. map_or ( false , |( kind, _, _ ) | kind == EntryKind :: Tree ) ;
173
175
while let Some ( name) = rela_path. next ( ) {
174
176
let name = name. as_ref ( ) ;
175
177
let is_last = rela_path. peek ( ) . is_none ( ) ;
@@ -206,7 +208,7 @@ impl<'a> Editor<'a> {
206
208
}
207
209
}
208
210
}
209
- Some ( ( kind, id) ) => {
211
+ Some ( ( kind, id, _mode ) ) => {
210
212
let entry = & mut cursor. entries [ idx] ;
211
213
if is_last {
212
214
// unconditionally overwrite what's there.
@@ -229,7 +231,7 @@ impl<'a> Editor<'a> {
229
231
}
230
232
Err ( insertion_idx) => match kind_and_id {
231
233
None => break ,
232
- Some ( ( kind, id) ) => {
234
+ Some ( ( kind, id, _mode ) ) => {
233
235
cursor. entries . insert (
234
236
insertion_idx,
235
237
tree:: Entry {
@@ -238,17 +240,14 @@ impl<'a> Editor<'a> {
238
240
oid : if is_last { id } else { id. kind ( ) . null ( ) } ,
239
241
} ,
240
242
) ;
241
- if is_last {
242
- break ;
243
- }
244
243
None
245
244
}
246
245
} ,
247
246
} ;
248
247
if needs_sorting {
249
248
cursor. entries . sort ( ) ;
250
249
}
251
- if is_last {
250
+ if is_last && kind_and_id . map_or ( false , | ( _ , _ , mode ) | mode == UpsertMode :: Normal ) {
252
251
break ;
253
252
}
254
253
push_path_component ( & mut self . path_buf , name) ;
@@ -279,6 +278,96 @@ impl<'a> Editor<'a> {
279
278
}
280
279
}
281
280
281
+ mod cursor {
282
+ use crate :: tree:: editor:: { Cursor , UpsertMode , WriteMode } ;
283
+ use crate :: tree:: { Editor , EntryKind } ;
284
+ use crate :: Tree ;
285
+ use bstr:: { BStr , BString } ;
286
+ use gix_hash:: ObjectId ;
287
+
288
+ /// Cursor handling
289
+ impl < ' a > Editor < ' a > {
290
+ /// Turn ourselves as a cursor, which points to the same tree as the editor.
291
+ ///
292
+ /// This is useful if a method takes a [`Cursor`], not an [`Editor`].
293
+ pub fn to_cursor ( & mut self ) -> Cursor < ' _ , ' a > {
294
+ Cursor {
295
+ parent : self ,
296
+ prefix : BString :: default ( ) ,
297
+ }
298
+ }
299
+
300
+ /// Create a cursor at the given `rela_path`, which must be a tree or is turned into a tree as its own edit.
301
+ ///
302
+ /// The returned cursor will then allow applying edits to the tree at `rela_path` as root.
303
+ /// 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 >
305
+ where
306
+ I : IntoIterator < Item = C > ,
307
+ C : AsRef < BStr > ,
308
+ {
309
+ self . path_buf . clear ( ) ;
310
+ self . upsert_or_remove_at_pathbuf (
311
+ rela_path,
312
+ Some ( ( EntryKind :: Tree , self . object_hash . null ( ) , UpsertMode :: AssureTreeOnly ) ) ,
313
+ ) ?;
314
+ Ok ( Cursor {
315
+ prefix : self . path_buf . clone ( ) , /* set during the upsert call */
316
+ parent : self ,
317
+ } )
318
+ }
319
+ }
320
+
321
+ impl < ' a , ' find > Cursor < ' a , ' find > {
322
+ /// 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 >
329
+ where
330
+ I : IntoIterator < Item = C > ,
331
+ C : AsRef < BStr > ,
332
+ {
333
+ self . parent . path_buf . clone_from ( & self . prefix ) ;
334
+ self . parent
335
+ . upsert_or_remove_at_pathbuf ( rela_path, Some ( ( kind, id, UpsertMode :: Normal ) ) ) ?;
336
+ Ok ( self )
337
+ }
338
+
339
+ /// Like [`crate()`], 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 >
341
+ where
342
+ I : IntoIterator < Item = C > ,
343
+ C : AsRef < BStr > ,
344
+ {
345
+ self . parent . path_buf . clone_from ( & self . prefix ) ;
346
+ self . parent . upsert_or_remove_at_pathbuf ( rela_path, None ) ?;
347
+ Ok ( self )
348
+ }
349
+
350
+ /// Like [`Editor::write()`], but will write only the subtree of the cursor.
351
+ pub fn write < E > ( & mut self , out : impl FnMut ( & Tree ) -> Result < ObjectId , E > ) -> Result < ObjectId , E > {
352
+ self . parent . path_buf . clone_from ( & self . prefix ) ;
353
+ self . parent . write_at_pathbuf ( out, WriteMode :: FromCursor )
354
+ }
355
+ }
356
+ }
357
+
358
+ #[ derive( Copy , Clone , Eq , PartialEq ) ]
359
+ enum UpsertMode {
360
+ Normal ,
361
+ /// Only make sure there is a tree at the given location (requires kind tree and null-id)
362
+ AssureTreeOnly ,
363
+ }
364
+
365
+ enum WriteMode {
366
+ Normal ,
367
+ /// Perform less cleanup to assure parent-editor still stays intact
368
+ FromCursor ,
369
+ }
370
+
282
371
fn cmp_entry_with_name ( a : & tree:: Entry , filename : & BStr , is_tree : bool ) -> Ordering {
283
372
let common = a. filename . len ( ) . min ( filename. len ( ) ) ;
284
373
a. filename [ ..common] . cmp ( & filename[ ..common] ) . then_with ( || {
0 commit comments