d02e545d Andy McCall 1 month ago ↶
1
2
3
4
5
6
7
8
9
ff0af1ee unknown 1 month ago
10
5b9c6ad4 Andy McCall 1 month ago ↶
11
use crate :: attest:: Attest;
ff0af1ee unknown 1 month ago
12
use crate :: hash:: { hash_object, header, ObjectRef, ObjectType} ;
1983686f Andy McCall 1 month ago ↶
13
use crate :: identity:: { Identity, Signature} ;
6f8aeb99 Andy McCall 1 month ago ↶
14
use crate :: objects:: { Blob, Change, ObjectError, StateObject} ;
fc1a466a Andy McCall 1 month ago ↶
15
use crate :: receipt:: Receipt;
ff0af1ee unknown 1 month ago
16
use rand:: RngCore;
d02e545d Andy McCall 1 month ago ↶
17
use std:: cell:: RefCell;
ff0af1ee unknown 1 month ago
18
use std:: fs;
d02e545d Andy McCall 1 month ago ↶
19
use std:: io:: { self , Read, Seek, SeekFrom, Write} ;
ff0af1ee unknown 1 month ago
20
use std:: path:: { Path, PathBuf} ;
21
d02e545d Andy McCall 1 month ago ↶
22
const PACK_MAGIC : & [ u8 ; 8 ] = b " OXYPACK1" ;
23
const IDX_MAGIC : & [ u8 ; 8 ] = b " OXYIDX01" ;
24
const IDX_ENTRY : usize = 32 + 8 + 4 + 4 + 1 + 1 ;
25
const FLAG_ZSTD : u8 = 1 ;
26
const ZSTD_LEVEL : i32 = 3 ;
da652d63 Andy McCall 1 month ago ↶
27
28
pub const REPACK_ZSTD_LEVEL : i32 = 9 ;
2b872d07 Andy McCall 1 month ago ↶
29
30
31
const MAX_OBJECT_LEN : u32 = 1 < < 30 ;
d02e545d Andy McCall 1 month ago ↶
32
afaba5f1 Andy McCall 1 month ago ↶
33
34
pub type Object = ( ObjectType, Vec< u8 > ) ;
35
d02e545d Andy McCall 1 month ago ↶
36
# [ derive ( Clone, Copy, Debug ) ]
37
struct PackEntry {
38
offset : u64 ,
39
stored_len : u32 ,
40
raw_len : u32 ,
41
ty : ObjectType,
42
flags : u8 ,
43
}
44
45
46
# [ derive ( Debug ) ]
47
struct PackIndex {
48
path : PathBuf,
49
refs : Vec< ObjectRef> ,
50
entries : Vec< PackEntry> ,
51
}
52
53
impl PackIndex {
54
fn load ( idx_path : & Path, pack_path : & Path) -> Result< PackIndex> {
55
let data = fs:: read( idx_path) ? ;
75167672 Andy McCall 1 month ago
56
let bad =
57
| m : & str | StoreError:: Corrupt( ObjectRef:: ZERO , format! ( " {} : {m} " , idx_path.display ( ) ) ) ;
d02e545d Andy McCall 1 month ago ↶
58
if data.len ( ) < 12 | | & data[ .. 8 ] ! = IDX_MAGIC {
59
return Err ( bad ( " bad index header" ) ) ;
60
}
61
let count = u32 :: from_le_bytes( data[ 8 .. 12 ] .try_into ( ) .unwrap ( ) ) as usize ;
62
if data.len ( ) ! = 12 + count * IDX_ENTRY {
63
return Err ( bad ( " index length mismatch" ) ) ;
64
}
2b872d07 Andy McCall 1 month ago ↶
65
let pack_len = fs:: metadata( pack_path) ? .len ( ) ;
d02e545d Andy McCall 1 month ago ↶
66
let mut refs = Vec :: with_capacity( count) ;
67
let mut entries = Vec :: with_capacity( count) ;
68
for i in 0 .. count {
69
let e = & data[ 12 + i * IDX_ENTRY .. 12 + ( i + 1 ) * IDX_ENTRY ] ;
70
let r = ObjectRef:: from_slice( & e[ .. 32 ] ) .unwrap ( ) ;
71
if let Some ( prev) = refs.last ( ) {
72
if r < = * prev {
73
return Err ( bad ( " index not sorted" ) ) ;
74
}
75
}
76
refs.push ( r) ;
2b872d07 Andy McCall 1 month ago ↶
77
let entry = PackEntry {
d02e545d Andy McCall 1 month ago ↶
78
offset: u64 :: from_le_bytes( e[ 32 .. 40 ] .try_into ( ) .unwrap ( ) ) ,
79
stored_len: u32 :: from_le_bytes( e[ 40 .. 44 ] .try_into ( ) .unwrap ( ) ) ,
80
raw_len: u32 :: from_le_bytes( e[ 44 .. 48 ] .try_into ( ) .unwrap ( ) ) ,
75167672 Andy McCall 1 month ago
81
ty: ObjectType:: ALL
82
.get ( e[ 48 ] as usize )
83
.copied ( )
84
.ok_or_else ( | | bad ( " unknown object type" ) ) ? ,
d02e545d Andy McCall 1 month ago ↶
85
flags: e[ 49 ] ,
2b872d07 Andy McCall 1 month ago ↶
86
} ;
a9f686ed Andy McCall 1 month ago ↶
87
if entry.stored_len > MAX_OBJECT_LEN | | entry.raw_len > MAX_OBJECT_LEN {
88
return Err ( StoreError:: Corrupt(
89
r,
90
format! (
91
" {} : the index claims an object of {} bytes ({} stored), more than the {MAX_OBJECT_LEN} an object may be" ,
92
idx_path.display ( ) ,
93
entry.raw_len,
94
entry.stored_len
95
) ,
96
) ) ;
97
}
98
if entry.offset.saturating_add ( entry.stored_len as u64 ) > pack_len {
2b872d07 Andy McCall 1 month ago ↶
99
return Err ( bad ( " index entry points outside the pack" ) ) ;
100
}
101
entries.push ( entry) ;
d02e545d Andy McCall 1 month ago ↶
102
}
75167672 Andy McCall 1 month ago
103
Ok ( PackIndex {
104
path: pack_path.to_path_buf ( ) ,
105
refs,
106
entries,
107
} )
d02e545d Andy McCall 1 month ago ↶
108
}
109
110
fn find ( & self , r : & ObjectRef) -> Option< PackEntry> {
111
self .refs.binary_search ( r) .ok ( ) .map ( | i | self .entries[ i] )
112
}
113
114
fn read ( & self , r : & ObjectRef, e : PackEntry) -> Result< ( ObjectType, Vec< u8 > ) > {
115
let mut f = fs:: File:: open( & self .path) ? ;
116
f.seek ( SeekFrom:: Start( e.offset) ) ? ;
117
let mut stored = vec! [ 0 u8 ; e.stored_len as usize ] ;
118
f.read_exact ( & mut stored) ? ;
119
let body = if e.flags & FLAG_ZSTD ! = 0 {
75167672 Andy McCall 1 month ago
120
zstd:: bulk:: decompress( & stored, e.raw_len as usize )
121
.map_err ( | err | StoreError:: Corrupt( * r, format! ( " zstd: {err} " ) ) ) ?
d02e545d Andy McCall 1 month ago ↶
122
} else {
123
stored
124
} ;
125
if body.len ( ) ! = e.raw_len as usize | | hash_object ( e.ty, & body) ! = * r {
75167672 Andy McCall 1 month ago
126
return Err ( StoreError:: Corrupt(
127
* r,
128
" packed object does not match its reference" .into ( ) ,
129
) ) ;
d02e545d Andy McCall 1 month ago ↶
130
}
131
Ok ( ( e.ty, body) )
132
}
133
}
134
135
fn type_index ( ty : ObjectType) -> u8 {
136
ObjectType:: ALL .iter ( ) .position ( | t | * t = = ty) .unwrap ( ) as u8
137
}
138
139
140
# [ derive ( Debug, Clone, Default, PartialEq, Eq ) ]
141
pub struct PackReport {
142
pub objects : usize ,
143
pub raw_bytes : u64 ,
144
pub packed_bytes : u64 ,
145
}
146
135212de Andy McCall 1 month ago ↶
147
148
pub const MAX_PACKS : usize = 16 ;
149
ba160f60 Andy McCall 1 month ago ↶
150
151
# [ derive ( Debug, Clone, Default, PartialEq, Eq ) ]
152
pub struct PruneReport {
153
pub objects : usize ,
154
pub bytes : u64 ,
155
156
pub deferred : usize ,
157
}
158
ff0af1ee unknown 1 month ago
159
# [ derive ( Debug, thiserror::Error ) ]
160
pub enum StoreError {
161
# [ error ( " io: {0}" ) ]
162
Io( # [ from ] io:: Error) ,
163
# [ error ( " corrupt object {0}: {1}" ) ]
164
Corrupt( ObjectRef, String ) ,
165
# [ error ( " object {0}" ) ]
166
Object( # [ from ] ObjectError) ,
167
# [ error ( " object {0} is a {1}, expected {2}" ) ]
168
WrongType( ObjectRef, & 'static str , & 'static str ) ,
169
# [ error ( " object {0} not found" ) ]
170
NotFound( ObjectRef) ,
6f8aeb99 Andy McCall 1 month ago ↶
171
# [ error ( " a {kind} object of {size} bytes is larger than the {limit} bytes an object may be; nothing was stored. A change this large adds or touches too many files at once: commit it in parts (a directory at a time)" ) ]
a9f686ed Andy McCall 1 month ago ↶
172
TooLarge {
173
kind: & 'static str ,
174
size: usize ,
175
limit: u32 ,
176
} ,
ff0af1ee unknown 1 month ago
177
}
178
179
pub type Result < T> = std:: result:: Result< T, StoreError> ;
180
d02e545d Andy McCall 1 month ago ↶
181
# [ derive ( Debug ) ]
ff0af1ee unknown 1 month ago
182
pub struct Store {
183
dir : PathBuf,
d02e545d Andy McCall 1 month ago ↶
184
packs : RefCell< Vec< PackIndex> > ,
8f10a07a Andy McCall 1 month ago ↶
185
186
187
pending : RefCell< Option< Pending> > ,
188
batch_depth : std:: cell:: Cell< u32 > ,
ce14f0d6 Andy McCall 1 month ago ↶
189
190
metas : RefCell< Option< crate:: fasthash:: FastMap< ObjectRef, crate:: state:: ChangeMeta> > > ,
8f10a07a Andy McCall 1 month ago ↶
191
}
192
193
194
195
196
# [ derive ( Debug ) ]
197
struct Pending {
198
buffered : Vec< ( ObjectType, Vec< u8 > ) > ,
199
buffered_bytes : usize ,
200
lookup : std:: collections:: HashMap< ObjectRef, PendingEntry> ,
201
pack : Option< PendingPack> ,
202
report : PackReport,
ff0af1ee unknown 1 month ago
203
}
204
8f10a07a Andy McCall 1 month ago ↶
205
# [ derive ( Debug, Clone, Copy ) ]
206
enum PendingEntry {
207
Buffered( usize ) ,
208
Packed( PackEntry) ,
209
}
210
211
# [ derive ( Debug ) ]
212
struct PendingPack {
213
id : String,
214
tmp_path : PathBuf,
215
writer : io:: BufWriter< fs:: File> ,
216
offset : u64 ,
217
index : Vec< ( ObjectRef, PackEntry) > ,
218
}
219
220
const SPILL_OBJECTS : usize = 256 ;
221
const SPILL_BYTES : usize = 8 < < 20 ;
222
223
224
pub struct Batch < 'a > {
225
store : & 'a Store,
226
done : bool ,
227
}
228
229
impl Batch < '_ > {
230
pub fn finish ( mut self ) -> Result< ( ) > {
231
self .done = true ;
232
self .store.finish_batch ( )
233
}
234
}
235
236
impl Drop for Batch < '_ > {
237
fn drop ( & mut self ) {
238
if ! self .done {
239
let _ = self .store.finish_batch ( ) ;
240
}
241
}
242
}
243
192fad1e Andy McCall 1 month ago ↶
244
245
fn write_pack_entry (
246
pack : & mut PendingPack,
247
r : & ObjectRef,
248
ty : ObjectType,
249
raw_len : usize ,
250
flags : u8 ,
251
stored : & [u8 ],
252
report : & mut PackReport,
253
) -> Result< PackEntry> {
254
let header_len = 1 + 1 + 4 + 4 + 32 ;
255
pack.writer.write_all ( & [ type_index ( ty) , flags] ) ? ;
256
pack.writer
257
.write_all ( & ( stored.len ( ) as u32 ) .to_le_bytes ( ) ) ? ;
258
pack.writer.write_all ( & ( raw_len as u32 ) .to_le_bytes ( ) ) ? ;
259
pack.writer.write_all ( & r.0 ) ? ;
260
pack.writer.write_all ( stored) ? ;
261
let entry = PackEntry {
262
offset: pack.offset + header_len,
263
stored_len: stored.len ( ) as u32 ,
264
raw_len: raw_len as u32 ,
265
ty,
266
flags,
267
} ;
268
pack.offset + = header_len + stored.len ( ) as u64 ;
269
pack.index.push ( ( * r, entry) ) ;
270
report.objects + = 1 ;
271
report.raw_bytes + = raw_len as u64 ;
272
report.packed_bytes + = stored.len ( ) as u64 ;
273
Ok ( entry)
274
}
275
ff0af1ee unknown 1 month ago
276
impl Store {
277
pub fn open ( dir : impl Into< PathBuf> ) -> io:: Result< Store> {
278
let dir = dir.into ( ) ;
279
fs:: create_dir_all( dir.join ( " loose" ) ) ? ;
75167672 Andy McCall 1 month ago
280
let store = Store {
281
dir,
282
packs: RefCell:: new( Vec :: new( ) ) ,
283
pending: RefCell:: new( None ) ,
284
batch_depth: std:: cell:: Cell:: new( 0 ) ,
ce14f0d6 Andy McCall 1 month ago ↶
285
metas: RefCell:: new( None ) ,
75167672 Andy McCall 1 month ago
286
} ;
287
store
288
.reload_packs ( )
289
.map_err ( | e | io:: Error:: other( e.to_string ( ) ) ) ? ;
d02e545d Andy McCall 1 month ago ↶
290
Ok ( store)
291
}
292
293
fn reload_packs ( & self ) -> Result< ( ) > {
294
let mut packs = Vec :: new( ) ;
295
let mut names: Vec< PathBuf> = fs:: read_dir( & self .dir) ?
296
.filter_map ( | e | e.ok ( ) .map ( | e | e.path ( ) ) )
75167672 Andy McCall 1 month ago
297
.filter ( | p | {
298
p.extension ( ) .is_some_and ( | x | x = = " idx" )
299
& & p.file_name ( )
300
.is_some_and ( | n | n.to_string_lossy ( ) .starts_with ( " pack-" ) )
301
} )
d02e545d Andy McCall 1 month ago ↶
302
.collect ( ) ;
303
names.sort ( ) ;
304
for idx in names {
305
let pack = idx.with_extension ( " pack" ) ;
306
if pack.is_file ( ) {
307
packs.push ( PackIndex:: load( & idx, & pack) ? ) ;
308
}
309
}
310
* self .packs.borrow_mut ( ) = packs;
311
Ok ( ( ) )
312
}
313
314
pub fn pack_count ( & self ) -> usize {
315
self .packs.borrow ( ) .len ( )
316
}
317
318
fn find_packed ( & self , r : & ObjectRef) -> Option< ( usize , PackEntry) > {
75167672 Andy McCall 1 month ago
319
self .packs
320
.borrow ( )
321
.iter ( )
322
.enumerate ( )
323
.find_map ( | ( i , p ) | p.find ( r) .map ( | e | ( i, e) ) )
d02e545d Andy McCall 1 month ago ↶
324
}
325
326
327
fn loose_objects ( & self ) -> io:: Result< Vec< ( ObjectRef, PathBuf) > > {
328
let mut out = Vec :: new( ) ;
329
let loose = self .dir.join ( " loose" ) ;
330
for d in fs:: read_dir( & loose) ? {
331
let d = d? ;
332
if ! d.file_type ( ) ? .is_dir ( ) {
333
continue ;
334
}
335
let prefix = d.file_name ( ) .to_string_lossy ( ) .to_string ( ) ;
336
for f in fs:: read_dir( d.path ( ) ) ? {
337
let f = f? ;
338
let name = f.file_name ( ) .to_string_lossy ( ) .to_string ( ) ;
339
if let Some ( r) = ObjectRef:: from_hex( & format! ( " {prefix} {name} " ) ) {
340
out.push ( ( r, f.path ( ) ) ) ;
341
}
342
}
343
}
344
out.sort ( ) ;
345
Ok ( out)
346
}
347
348
pub fn loose_count ( & self ) -> io:: Result< usize > {
349
Ok ( self .loose_objects ( ) ? .len ( ) )
350
}
351
352
353
354
pub fn pack_loose ( & self ) -> Result< PackReport> {
355
let loose = self .loose_objects ( ) ? ;
75167672 Andy McCall 1 month ago
356
self .write_pack (
357
& loose.iter ( ) .map ( | ( r , _) | * r) .collect:: < Vec< _ > > ( ) ,
358
& loose.iter ( ) .map ( | ( _, p ) | p.clone ( ) ) .collect:: < Vec< _ > > ( ) ,
359
& [ ] ,
da652d63 Andy McCall 1 month ago ↶
360
ZSTD_LEVEL ,
75167672 Andy McCall 1 month ago
361
)
da652d63 Andy McCall 1 month ago ↶
362
}
363
364
365
366
367
pub fn repack_all ( & self , level : i32 ) -> Result< ( PackReport, usize ) > {
368
let loose = self .loose_objects ( ) ? ;
369
let ( mut refs, paths) : ( Vec< ObjectRef> , Vec< PathBuf> ) = {
370
let packs = self .packs.borrow ( ) ;
371
(
372
packs.iter ( ) .flat_map ( | p | p.refs.iter ( ) .copied ( ) ) .collect ( ) ,
373
packs.iter ( ) .map ( | p | p.path.clone ( ) ) .collect ( ) ,
374
)
375
} ;
376
if loose.is_empty ( ) & & paths.len ( ) < = 1 & & level = = ZSTD_LEVEL {
377
return Ok ( ( PackReport:: default( ) , 0 ) ) ;
378
}
379
refs.extend ( loose.iter ( ) .map ( | ( r , _) | * r) ) ;
380
let loose_paths: Vec< PathBuf> = loose.into_iter ( ) .map ( | ( _, p ) | p ) .collect ( ) ;
381
let n = paths.len ( ) ;
382
let report = self .write_pack ( & refs, & loose_paths, & paths, level) ? ;
383
Ok ( ( report, n) )
ba160f60 Andy McCall 1 month ago ↶
384
}
385
135212de Andy McCall 1 month ago ↶
386
387
388
pub fn merge_packs ( & self ) -> Result< usize > {
389
let ( refs, paths) : ( Vec< ObjectRef> , Vec< PathBuf> ) = {
390
let packs = self .packs.borrow ( ) ;
391
if packs.len ( ) < = MAX_PACKS {
392
return Ok ( 0 ) ;
393
}
75167672 Andy McCall 1 month ago
394
(
395
packs.iter ( ) .flat_map ( | p | p.refs.iter ( ) .copied ( ) ) .collect ( ) ,
396
packs.iter ( ) .map ( | p | p.path.clone ( ) ) .collect ( ) ,
397
)
135212de Andy McCall 1 month ago ↶
398
} ;
399
let n = paths.len ( ) ;
da652d63 Andy McCall 1 month ago ↶
400
self .write_pack ( & refs, & [ ] , & paths, ZSTD_LEVEL ) ? ;
135212de Andy McCall 1 month ago ↶
401
Ok ( n)
402
}
403
ba160f60 Andy McCall 1 month ago ↶
404
405
406
407
408
75167672 Andy McCall 1 month ago
409
pub fn prune (
410
& self ,
411
keep : & std:: collections:: HashSet< ObjectRef> ,
412
grace : std:: time:: Duration,
413
) -> Result< PruneReport> {
ba160f60 Andy McCall 1 month ago ↶
414
let now = std:: time:: SystemTime:: now( ) ;
75167672 Andy McCall 1 month ago
415
let old_enough = | m : & fs:: Metadata| {
416
m.modified ( )
417
.map ( | t | now.duration_since ( t) .unwrap_or_default ( ) > = grace )
418
.unwrap_or ( true )
419
} ;
ba160f60 Andy McCall 1 month ago ↶
420
let mut report = PruneReport:: default( ) ;
421
422
for ( r, path) in self .loose_objects ( ) ? {
423
if keep.contains ( & r) {
424
continue ;
425
}
426
let meta = fs:: metadata( & path) ? ;
427
if ! old_enough ( & meta) {
428
report.deferred + = 1 ;
429
continue ;
430
}
431
report.bytes + = meta.len ( ) ;
432
report.objects + = 1 ;
b30a880f Andy McCall 1 month ago ↶
433
434
ba160f60 Andy McCall 1 month ago ↶
435
fs:: remove_file( & path) ? ;
436
self .remove_sidecars ( & r) ? ;
437
}
438
439
let mut keep_refs: Vec< ObjectRef> = Vec :: new( ) ;
440
let mut old_paths: Vec< PathBuf> = Vec :: new( ) ;
441
let mut rewrite = false ;
442
for p in self .packs.borrow ( ) .iter ( ) {
443
let meta = fs:: metadata( & p.path) ? ;
444
let unreachable = p.refs.iter ( ) .filter ( | r | ! keep.contains ( r) ) .count ( ) ;
445
if unreachable = = 0 {
446
continue ;
447
}
448
if ! old_enough ( & meta) {
449
report.deferred + = unreachable;
450
continue ;
451
}
452
rewrite = true ;
453
old_paths.push ( p.path.clone ( ) ) ;
454
for ( r, e) in p.refs.iter ( ) .zip ( p.entries.iter ( ) ) {
455
if keep.contains ( r) {
456
keep_refs.push ( * r) ;
457
} else {
458
report.objects + = 1 ;
459
report.bytes + = e.stored_len as u64 ;
460
}
461
}
462
}
463
if rewrite {
464
let dropped: Vec< ObjectRef> = {
465
let packs = self .packs.borrow ( ) ;
75167672 Andy McCall 1 month ago
466
packs
467
.iter ( )
468
.filter ( | p | old_paths.contains ( & p.path) )
469
.flat_map ( | p | p.refs.iter ( ) .copied ( ) )
470
.filter ( | r | ! keep.contains ( r) )
471
.collect ( )
ba160f60 Andy McCall 1 month ago ↶
472
} ;
da652d63 Andy McCall 1 month ago ↶
473
self .write_pack ( & keep_refs, & [ ] , & old_paths, ZSTD_LEVEL ) ? ;
ba160f60 Andy McCall 1 month ago ↶
474
for r in dropped {
475
self .remove_sidecars ( & r) ? ;
476
}
477
}
478
Ok ( report)
479
}
480
481
fn remove_sidecars ( & self , r : & ObjectRef) -> Result< ( ) > {
fc1a466a Andy McCall 1 month ago ↶
482
let sidecars = [
483
self .sig_path ( r) ,
484
self .index_path ( " seals" , r) ,
485
self .index_path ( " receipts" , r) ,
486
] ;
487
for path in sidecars {
ba160f60 Andy McCall 1 month ago ↶
488
match fs:: remove_file( & path) {
489
Ok ( ( ) ) => {
490
let _ = fs:: remove_dir( path.parent ( ) .unwrap ( ) ) ;
491
}
492
Err ( e) if e.kind ( ) = = io:: ErrorKind:: NotFound => { }
493
Err ( e) => return Err ( e.into ( ) ) ,
494
}
495
}
496
Ok ( ( ) )
497
}
498
499
8f10a07a Andy McCall 1 month ago ↶
500
501
75167672 Andy McCall 1 month ago
502
fn finish_pack_files (
503
& self ,
504
id : & str ,
505
tmp_pack : & Path,
506
index : & [( ObjectRef , PackEntry ) ],
507
) -> Result< ( ) > {
8f10a07a Andy McCall 1 month ago ↶
508
let pack_path = self .dir.join ( format! ( " pack-{id} .pack" ) ) ;
509
let idx_path = self .dir.join ( format! ( " pack-{id} .idx" ) ) ;
510
let tmp_idx = self .dir.join ( format! ( " tmp-{id} .idx" ) ) ;
511
{
512
let mut f = io:: BufWriter:: new( fs:: File:: create( & tmp_idx) ? ) ;
513
f.write_all ( IDX_MAGIC ) ? ;
514
f.write_all ( & ( index.len ( ) as u32 ) .to_le_bytes ( ) ) ? ;
515
for ( r, e) in index {
516
f.write_all ( & r.0 ) ? ;
517
f.write_all ( & e.offset.to_le_bytes ( ) ) ? ;
518
f.write_all ( & e.stored_len.to_le_bytes ( ) ) ? ;
519
f.write_all ( & e.raw_len.to_le_bytes ( ) ) ? ;
520
f.write_all ( & [ type_index ( e.ty) , e.flags] ) ? ;
521
}
522
f.flush ( ) ? ;
523
f.get_ref ( ) .sync_all ( ) ? ;
524
}
4dcb8c09 Andy McCall 1 month ago ↶
525
526
crate :: durable:: rename( tmp_pack, & pack_path) ? ;
527
crate :: durable:: rename( & tmp_idx, & idx_path) ? ;
8f10a07a Andy McCall 1 month ago ↶
528
self .reload_packs ( )
529
}
530
75167672 Andy McCall 1 month ago
531
fn write_pack (
532
& self ,
533
refs : & [ObjectRef],
534
loose_paths : & [PathBuf],
535
old_packs : & [PathBuf],
da652d63 Andy McCall 1 month ago ↶
536
level : i32 ,
75167672 Andy McCall 1 month ago
537
) -> Result< PackReport> {
ba160f60 Andy McCall 1 month ago ↶
538
if refs.is_empty ( ) {
539
for p in old_packs {
540
fs:: remove_file( p) ? ;
541
fs:: remove_file( p.with_extension ( " idx" ) ) ? ;
542
}
543
self .reload_packs ( ) ? ;
d02e545d Andy McCall 1 month ago ↶
544
return Ok ( PackReport:: default( ) ) ;
545
}
ba160f60 Andy McCall 1 month ago ↶
546
let mut refs: Vec< ObjectRef> = refs.to_vec ( ) ;
547
refs.sort ( ) ;
548
refs.dedup ( ) ;
d02e545d Andy McCall 1 month ago ↶
549
let mut nonce = [ 0 u8 ; 8 ] ;
550
rand:: rngs:: OsRng.fill_bytes ( & mut nonce) ;
551
let id = hex:: encode( nonce) ;
552
let tmp_pack = self .dir.join ( format! ( " tmp-{id} .pack" ) ) ;
553
let mut report = PackReport:: default( ) ;
ba160f60 Andy McCall 1 month ago ↶
554
let mut index: Vec< ( ObjectRef, PackEntry) > = Vec :: with_capacity( refs.len ( ) ) ;
d02e545d Andy McCall 1 month ago ↶
555
{
556
let mut f = io:: BufWriter:: new( fs:: File:: create( & tmp_pack) ? ) ;
557
f.write_all ( PACK_MAGIC ) ? ;
558
let mut offset = PACK_MAGIC .len ( ) as u64 ;
da652d63 Andy McCall 1 month ago ↶
559
560
561
let mut batches = refs.chunks ( 1024 ) ;
562
let mut compressed_batch = Vec :: new( ) ;
563
loop {
564
if compressed_batch.is_empty ( ) {
565
let Some ( batch) = batches.next ( ) else { break } ;
566
let mut read: Vec< ( ObjectRef, ObjectType, Vec< u8 > , bool ) > =
567
Vec :: with_capacity( batch.len ( ) ) ;
568
for r in batch {
569
let ( ty, body) = self .get ( r) ? ;
570
read.push ( ( * r, ty, body, self .is_nocompress ( r) ) ) ;
75167672 Andy McCall 1 month ago
571
}
da652d63 Andy McCall 1 month ago ↶
572
use rayon:: prelude:: * ;
573
compressed_batch = read
574
.into_par_iter ( )
575
.map ( | ( r , ty , body , raw ) | {
576
let packed = if raw {
577
None
578
} else {
579
zstd:: bulk:: compress( & body, level)
580
.ok ( )
581
.filter ( | c | c.len ( ) < body.len ( ) )
582
} ;
583
( r, ty, body, packed)
584
} )
585
.collect:: < Vec< _ > > ( ) ;
586
compressed_batch.reverse ( ) ;
587
}
588
let Some ( ( r, ty, body, packed) ) = compressed_batch.pop ( ) else {
589
continue ;
590
} ;
591
let r = & r;
592
let ( flags, stored) = match packed {
593
Some ( c) => ( FLAG_ZSTD , c) ,
594
None => ( 0 , body.clone ( ) ) ,
cdb5beba Andy McCall 1 month ago ↶
595
} ;
d02e545d Andy McCall 1 month ago ↶
596
let header_len = 1 + 1 + 4 + 4 + 32 ;
597
f.write_all ( & [ type_index ( ty) , flags] ) ? ;
598
f.write_all ( & ( stored.len ( ) as u32 ) .to_le_bytes ( ) ) ? ;
599
f.write_all ( & ( body.len ( ) as u32 ) .to_le_bytes ( ) ) ? ;
600
f.write_all ( & r.0 ) ? ;
601
f.write_all ( & stored) ? ;
75167672 Andy McCall 1 month ago
602
index.push ( (
603
* r,
604
PackEntry {
605
offset: offset + header_len,
606
stored_len: stored.len ( ) as u32 ,
607
raw_len: body.len ( ) as u32 ,
608
ty,
609
flags,
610
} ,
611
) ) ;
d02e545d Andy McCall 1 month ago ↶
612
offset + = header_len + stored.len ( ) as u64 ;
613
report.objects + = 1 ;
614
report.raw_bytes + = body.len ( ) as u64 ;
615
report.packed_bytes + = stored.len ( ) as u64 ;
616
}
617
f.flush ( ) ? ;
618
f.get_ref ( ) .sync_all ( ) ? ;
619
}
8f10a07a Andy McCall 1 month ago ↶
620
self .finish_pack_files ( & id, & tmp_pack, & index) ? ;
ba160f60 Andy McCall 1 month ago ↶
621
for p in old_packs {
622
fs:: remove_file( p) ? ;
623
fs:: remove_file( p.with_extension ( " idx" ) ) ? ;
624
}
d02e545d Andy McCall 1 month ago ↶
625
self .reload_packs ( ) ? ;
b30a880f Andy McCall 1 month ago ↶
626
ba160f60 Andy McCall 1 month ago ↶
627
for path in loose_paths {
d02e545d Andy McCall 1 month ago ↶
628
fs:: remove_file( path) ? ;
629
}
630
Ok ( report)
ff0af1ee unknown 1 month ago
631
}
632
633
pub fn dir ( & self ) -> & Path {
634
& self .dir
635
}
636
387335ff Andy McCall 1 month ago ↶
637
pub ( crate ) fn path_for ( & self , r : & ObjectRef) -> PathBuf {
ff0af1ee unknown 1 month ago
638
let h = r.to_hex ( ) ;
639
self .dir.join ( " loose" ) .join ( & h[ .. 2 ] ) .join ( & h[ 2 .. ] )
640
}
641
642
pub fn has ( & self , r : & ObjectRef) -> bool {
75167672 Andy McCall 1 month ago
643
self .path_for ( r) .is_file ( )
644
| | self .find_packed ( r) .is_some ( )
645
| | self
646
.pending
647
.borrow ( )
648
.as_ref ( )
649
.is_some_and ( | p | p.lookup.contains_key ( r) )
ff0af1ee unknown 1 month ago
650
}
651
8f10a07a Andy McCall 1 month ago ↶
652
653
654
655
656
pub fn begin_batch ( & self ) -> Result< Batch< '_ > > {
657
let depth = self .batch_depth.get ( ) ;
658
if depth = = 0 {
75167672 Andy McCall 1 month ago
659
* self .pending.borrow_mut ( ) = Some ( Pending {
660
buffered: vec! [ ] ,
661
buffered_bytes: 0 ,
662
lookup: Default :: default( ) ,
663
pack: None ,
664
report: PackReport:: default( ) ,
665
} ) ;
8f10a07a Andy McCall 1 month ago ↶
666
}
667
self .batch_depth.set ( depth + 1 ) ;
75167672 Andy McCall 1 month ago
668
Ok ( Batch {
669
store: self ,
670
done: false ,
671
} )
8f10a07a Andy McCall 1 month ago ↶
672
}
673
674
fn finish_batch ( & self ) -> Result< ( ) > {
675
let depth = self .batch_depth.get ( ) ;
676
if depth = = 0 {
677
return Ok ( ( ) ) ;
678
}
679
self .batch_depth.set ( depth - 1 ) ;
680
if depth > 1 {
681
return Ok ( ( ) ) ;
682
}
75167672 Andy McCall 1 month ago
683
let Some ( pending) = self .pending.borrow_mut ( ) .take ( ) else {
684
return Ok ( ( ) ) ;
685
} ;
4dcb8c09 Andy McCall 1 month ago ↶
686
self .write_pending ( pending)
687
}
688
689
690
691
692
pub fn flush ( & self ) -> Result< ( ) > {
693
let Some ( pending) = self .pending.borrow_mut ( ) .take ( ) else {
694
return Ok ( ( ) ) ;
695
} ;
696
self .write_pending ( pending) ? ;
697
* self .pending.borrow_mut ( ) = Some ( Pending {
698
buffered: vec! [ ] ,
699
buffered_bytes: 0 ,
700
lookup: Default :: default( ) ,
701
pack: None ,
702
report: PackReport:: default( ) ,
703
} ) ;
704
Ok ( ( ) )
705
}
706
707
fn write_pending ( & self , pending : Pending) -> Result< ( ) > {
8f10a07a Andy McCall 1 month ago ↶
708
match pending.pack {
709
None => {
b30a880f Andy McCall 1 month ago ↶
710
let objects: Vec< ( ObjectType, & [u8 ]) > = pending
711
.buffered
712
.iter ( )
713
.map ( | ( ty , body ) | ( * ty, body.as_slice ( ) ) )
714
.collect ( ) ;
715
self .put_loose_many ( & objects)
8f10a07a Andy McCall 1 month ago ↶
716
}
717
Some ( mut pack) => {
718
pack.writer.flush ( ) ? ;
719
pack.writer.get_ref ( ) .sync_all ( ) ? ;
720
drop ( pack.writer) ;
721
pack.index.sort_by_key ( | ( r , _) | * r) ;
722
self .finish_pack_files ( & pack.id, & pack.tmp_path, & pack.index) ? ;
723
Ok ( ( ) )
724
}
725
}
726
}
727
728
729
fn pending_get ( & self , r : & ObjectRef) -> Option< Result< ( ObjectType, Vec< u8 > ) > > {
730
let mut guard = self .pending.borrow_mut ( ) ;
731
let pending = guard.as_mut ( ) ? ;
732
let entry = * pending.lookup.get ( r) ? ;
733
Some ( match entry {
734
PendingEntry:: Buffered( i) => {
735
let ( ty, body) = & pending.buffered[ i] ;
736
Ok ( ( * ty, body.clone ( ) ) )
737
}
738
PendingEntry:: Packed( e) => {
75167672 Andy McCall 1 month ago
739
let Some ( pack) = pending.pack.as_mut ( ) else {
740
return Some ( Err ( StoreError:: NotFound( * r) ) ) ;
741
} ;
8f10a07a Andy McCall 1 month ago ↶
742
match pack.writer.flush ( ) {
75167672 Andy McCall 1 month ago
743
Ok ( ( ) ) => PackIndex {
744
path: pack.tmp_path.clone ( ) ,
745
refs: vec! [ ] ,
746
entries: vec! [ ] ,
747
}
748
.read ( r, e) ,
8f10a07a Andy McCall 1 month ago ↶
749
Err ( err) => Err ( err.into ( ) ) ,
750
}
751
}
752
} )
753
}
754
755
75167672 Andy McCall 1 month ago
756
fn pending_pack_write (
757
& self ,
758
pack : & mut PendingPack,
759
r : & ObjectRef,
760
ty : ObjectType,
761
body : & [u8 ],
762
report : & mut PackReport,
763
) -> Result< PackEntry> {
8f10a07a Andy McCall 1 month ago ↶
764
let ( flags, stored) = if self .is_nocompress ( r) {
765
( 0 , body.to_vec ( ) )
766
} else {
75167672 Andy McCall 1 month ago
767
let compressed = zstd:: bulk:: compress( body, ZSTD_LEVEL )
768
.map_err ( | e | StoreError:: Corrupt( * r, format! ( " zstd: {e} " ) ) ) ? ;
769
if compressed.len ( ) < body.len ( ) {
770
( FLAG_ZSTD , compressed)
771
} else {
772
( 0 , body.to_vec ( ) )
773
}
8f10a07a Andy McCall 1 month ago ↶
774
} ;
192fad1e Andy McCall 1 month ago ↶
775
write_pack_entry ( pack, r, ty, body.len ( ) , flags, & stored, report)
8f10a07a Andy McCall 1 month ago ↶
776
}
777
778
fn put_pending ( & self , r : ObjectRef, ty : ObjectType, body : & [u8 ]) -> Result< ( ) > {
779
let mut guard = self .pending.borrow_mut ( ) ;
780
let pending = guard.as_mut ( ) .expect ( " batch open" ) ;
781
if pending.lookup.contains_key ( & r) {
782
return Ok ( ( ) ) ;
783
}
75167672 Andy McCall 1 month ago
784
if pending.pack.is_none ( )
785
& & pending.buffered.len ( ) < SPILL_OBJECTS
786
& & pending.buffered_bytes + body.len ( ) < = SPILL_BYTES
787
{
8f10a07a Andy McCall 1 month ago ↶
788
pending.buffered.push ( ( ty, body.to_vec ( ) ) ) ;
789
pending.buffered_bytes + = body.len ( ) ;
75167672 Andy McCall 1 month ago
790
pending
791
.lookup
792
.insert ( r, PendingEntry:: Buffered( pending.buffered.len ( ) - 1 ) ) ;
8f10a07a Andy McCall 1 month ago ↶
793
return Ok ( ( ) ) ;
794
}
192fad1e Andy McCall 1 month ago ↶
795
self .ensure_pack ( pending) ? ;
796
let mut report = std:: mem:: take( & mut pending.report) ;
797
let pack = pending.pack.as_mut ( ) .unwrap ( ) ;
798
let e = self .pending_pack_write ( pack, & r, ty, body, & mut report) ? ;
799
pending.report = report;
800
pending.lookup.insert ( r, PendingEntry:: Packed( e) ) ;
801
Ok ( ( ) )
802
}
803
804
805
806
fn ensure_pack ( & self , pending : & mut Pending) -> Result< ( ) > {
8f10a07a Andy McCall 1 month ago ↶
807
if pending.pack.is_none ( ) {
808
809
let mut nonce = [ 0 u8 ; 8 ] ;
810
rand:: rngs:: OsRng.fill_bytes ( & mut nonce) ;
811
let id = hex:: encode( nonce) ;
812
let tmp_path = self .dir.join ( format! ( " tmp-{id} .pack" ) ) ;
813
let mut writer = io:: BufWriter:: new( fs:: File:: create( & tmp_path) ? ) ;
814
writer.write_all ( PACK_MAGIC ) ? ;
75167672 Andy McCall 1 month ago
815
let mut pack = PendingPack {
816
id,
817
tmp_path,
818
writer,
819
offset: PACK_MAGIC .len ( ) as u64 ,
820
index: vec! [ ] ,
821
} ;
8f10a07a Andy McCall 1 month ago ↶
822
let buffered = std:: mem:: take( & mut pending.buffered) ;
823
pending.buffered_bytes = 0 ;
824
let mut report = std:: mem:: take( & mut pending.report) ;
825
for ( bty, bbody) in & buffered {
826
let br = hash_object ( * bty, bbody) ;
827
let e = self .pending_pack_write ( & mut pack, & br, * bty, bbody, & mut report) ? ;
828
pending.lookup.insert ( br, PendingEntry:: Packed( e) ) ;
829
}
830
pending.report = report;
831
pending.pack = Some ( pack) ;
192fad1e Andy McCall 1 month ago ↶
832
}
833
Ok ( ( ) )
834
}
835
836
837
838
839
pub fn put_many ( & self , objects : & [( ObjectType , &[u8 ]) ]) -> Result< Vec< ObjectRef> > {
840
use rayon:: prelude:: * ;
841
let total: usize = objects.iter ( ) .map ( | ( _, b ) | b.len ( ) ) .sum ( ) ;
842
if self .pending.borrow ( ) .is_none ( ) | | total < SPILL_BYTES {
843
return objects.iter ( ) .map ( | ( ty , b ) | self .put ( * ty, b) ) .collect ( ) ;
844
}
845
for ( ty, b) in objects {
846
if b.len ( ) > MAX_OBJECT_LEN as usize {
847
return Err ( StoreError:: TooLarge {
848
kind: ty.name ( ) ,
849
size: b.len ( ) ,
850
limit: MAX_OBJECT_LEN ,
851
} ) ;
852
}
8f10a07a Andy McCall 1 month ago ↶
853
}
192fad1e Andy McCall 1 month ago ↶
854
let refs: Vec< ObjectRef> = objects
855
.par_iter ( )
856
.map ( | ( ty , b ) | hash_object ( * ty, b) )
857
.collect ( ) ;
858
859
let mut seen: std:: collections:: HashSet< ObjectRef> = std:: collections:: HashSet:: new( ) ;
860
let mut todo: Vec< usize > = Vec :: new( ) ;
861
for ( i, r) in refs.iter ( ) .enumerate ( ) {
862
let known = self
863
.pending
864
.borrow ( )
865
.as_ref ( )
866
.is_some_and ( | p | p.lookup.contains_key ( r) ) ;
867
if seen.insert ( * r)
868
& & ! known
869
& & ! self .path_for ( r) .is_file ( )
870
& & self .find_packed ( r) .is_none ( )
871
{
872
todo.push ( i) ;
873
}
874
}
875
let raw: std:: collections:: HashSet< ObjectRef> =
876
match fs:: read_dir( self .dir.join ( " nocompress" ) ) {
877
Ok ( rd) => rd
878
.flatten ( )
879
.filter_map ( | e | ObjectRef:: from_hex( & e.file_name ( ) .to_string_lossy ( ) ) )
880
.collect ( ) ,
881
Err ( _ ) => std:: collections:: HashSet:: new( ) ,
882
} ;
883
let packed: Vec< ( u8 , Vec< u8 > ) > = todo
884
.par_iter ( )
885
.map ( | i | {
886
let body = objects[ * i] .1 ;
887
if raw.contains ( & refs[ * i] ) {
888
return ( 0 , body.to_vec ( ) ) ;
889
}
890
match zstd:: bulk:: compress( body, ZSTD_LEVEL ) {
891
Ok ( c) if c.len ( ) < body.len ( ) => ( FLAG_ZSTD , c) ,
892
_ => ( 0 , body.to_vec ( ) ) ,
893
}
894
} )
895
.collect ( ) ;
896
let mut guard = self .pending.borrow_mut ( ) ;
897
let pending = guard.as_mut ( ) .expect ( " batch open" ) ;
898
self .ensure_pack ( pending) ? ;
8f10a07a Andy McCall 1 month ago ↶
899
let mut report = std:: mem:: take( & mut pending.report) ;
900
let pack = pending.pack.as_mut ( ) .unwrap ( ) ;
192fad1e Andy McCall 1 month ago ↶
901
for ( i, ( flags, stored) ) in todo.iter ( ) .zip ( packed) {
902
let ( ty, body) = objects[ * i] ;
903
let e = write_pack_entry ( pack, & refs[ * i] , ty, body.len ( ) , flags, & stored, & mut report) ? ;
904
pending.lookup.insert ( refs[ * i] , PendingEntry:: Packed( e) ) ;
905
}
8f10a07a Andy McCall 1 month ago ↶
906
pending.report = report;
192fad1e Andy McCall 1 month ago ↶
907
Ok ( refs)
8f10a07a Andy McCall 1 month ago ↶
908
}
909
ff0af1ee unknown 1 month ago
910
911
pub fn put ( & self , ty : ObjectType, body : & [u8 ]) -> Result< ObjectRef> {
a9f686ed Andy McCall 1 month ago ↶
912
913
914
915
if body.len ( ) > MAX_OBJECT_LEN as usize {
916
return Err ( StoreError:: TooLarge {
917
kind: ty.name ( ) ,
918
size: body.len ( ) ,
919
limit: MAX_OBJECT_LEN ,
920
} ) ;
921
}
ff0af1ee unknown 1 month ago
922
let r = hash_object ( ty, body) ;
923
let path = self .path_for ( & r) ;
d02e545d Andy McCall 1 month ago ↶
924
if path.is_file ( ) | | self .find_packed ( & r) .is_some ( ) {
ff0af1ee unknown 1 month ago
925
return Ok ( r) ;
926
}
8f10a07a Andy McCall 1 month ago ↶
927
if self .pending.borrow ( ) .is_some ( ) {
928
self .put_pending ( r, ty, body) ? ;
929
return Ok ( r) ;
930
}
931
self .put_loose ( ty, body) ? ;
932
Ok ( r)
933
}
934
935
936
fn put_loose ( & self , ty : ObjectType, body : & [u8 ]) -> Result< ObjectRef> {
937
let r = hash_object ( ty, body) ;
938
let path = self .path_for ( & r) ;
939
if path.is_file ( ) {
940
return Ok ( r) ;
941
}
b30a880f Andy McCall 1 month ago ↶
942
if ! path.parent ( ) .unwrap ( ) .is_dir ( ) {
943
self .ensure_fanouts ( ) ? ;
4dcb8c09 Andy McCall 1 month ago ↶
944
}
ff0af1ee unknown 1 month ago
945
let mut nonce = [ 0 u8 ; 8 ] ;
946
rand:: rngs:: OsRng.fill_bytes ( & mut nonce) ;
75167672 Andy McCall 1 month ago
947
let tmp = self
948
.dir
949
.join ( " loose" )
950
.join ( format! ( " tmp-{} " , hex:: encode( nonce) ) ) ;
4dcb8c09 Andy McCall 1 month ago ↶
951
crate :: durable:: step( " write loose object" ) ? ;
ff0af1ee unknown 1 month ago
952
{
953
let mut f = fs:: File:: create( & tmp) ? ;
954
f.write_all ( & header ( ty, body.len ( ) ) ) ? ;
955
f.write_all ( body) ? ;
956
f.sync_all ( ) ? ;
957
}
4dcb8c09 Andy McCall 1 month ago ↶
958
crate :: durable:: rename( & tmp, & path) ? ;
ff0af1ee unknown 1 month ago
959
Ok ( r)
b30a880f Andy McCall 1 month ago ↶
960
}
961
962
963
964
965
966
fn ensure_fanouts ( & self ) -> io:: Result< ( ) > {
967
let loose = self .dir.join ( " loose" ) ;
968
for b in 0 .. = 255 u8 {
969
match fs:: create_dir( loose.join ( format! ( " {b:02x} " ) ) ) {
970
Ok ( ( ) ) => { }
971
Err ( e) if e.kind ( ) = = io:: ErrorKind:: AlreadyExists => { }
972
Err ( e) => return Err ( e) ,
973
}
974
}
975
crate :: durable:: sync_dir( & loose)
976
}
977
978
979
980
981
982
fn put_loose_many ( & self , objects : & [( ObjectType , &[u8 ]) ]) -> Result< ( ) > {
983
use rayon:: prelude:: * ;
984
if objects.len ( ) < = 1 {
985
for ( ty, body) in objects {
986
self .put_loose ( * ty, body) ? ;
987
}
988
return Ok ( ( ) ) ;
989
}
990
let mut seen = std:: collections:: HashSet:: new( ) ;
991
let todo: Vec< ( PathBuf, PathBuf, ObjectType, & [u8 ]) > = objects
992
.iter ( )
993
.filter_map ( | ( ty , body ) | {
994
let r = hash_object ( * ty, body) ;
995
let path = self .path_for ( & r) ;
996
( seen.insert ( r) & & ! path.is_file ( ) ) .then ( | | {
997
let mut nonce = [ 0 u8 ; 8 ] ;
998
rand:: rngs:: OsRng.fill_bytes ( & mut nonce) ;
999
let tmp = self
1000
.dir
1001
.join ( " loose" )
1002
.join ( format! ( " tmp-{} " , hex:: encode( nonce) ) ) ;
1003
( path, tmp, * ty, * body)
1004
} )
1005
} )
1006
.collect ( ) ;
1007
if todo.is_empty ( ) {
1008
return Ok ( ( ) ) ;
1009
}
1010
let fanouts: std:: collections:: BTreeSet< & Path> =
1011
todo.iter ( ) .map ( | ( p , ..) | p.parent ( ) .unwrap ( ) ) .collect ( ) ;
1012
if fanouts.iter ( ) .any ( | f | ! f.is_dir ( ) ) {
1013
self .ensure_fanouts ( ) ? ;
1014
}
1015
crate :: durable:: step( " write loose objects" ) ? ;
1016
todo.par_iter ( )
1017
.try_for_each ( | ( _, tmp , ty , body ) | -> io:: Result< ( ) > {
1018
let mut f = fs:: File:: create( tmp) ? ;
1019
f.write_all ( & header ( * ty, body.len ( ) ) ) ? ;
1020
f.write_all ( body) ? ;
1021
f.sync_all ( )
1022
} ) ? ;
1023
crate :: durable:: step( " rename loose objects" ) ? ;
1024
for ( path, tmp, .. ) in & todo {
1025
fs:: rename( tmp, path) ? ;
1026
}
1027
fanouts
1028
.par_iter ( )
1029
.try_for_each ( | d | crate :: durable:: sync_dir( d) ) ? ;
1030
Ok ( ( ) )
ff0af1ee unknown 1 month ago
1031
}
1032
8f10a07a Andy McCall 1 month ago ↶
1033
ff0af1ee unknown 1 month ago
1034
pub fn get ( & self , r : & ObjectRef) -> Result< ( ObjectType, Vec< u8 > ) > {
afaba5f1 Andy McCall 1 month ago ↶
1035
match read_loose_at ( & self .path_for ( r) , r) {
1036
Some ( found) => found,
1037
None => match self .find_packed ( r) {
1038
Some ( ( i, e) ) => self .packs.borrow ( ) [ i] .read ( r, e) ,
1039
None => self .pending_get ( r) .unwrap_or ( Err ( StoreError:: NotFound( * r) ) ) ,
1040
} ,
1041
}
1042
}
1043
1044
1045
1046
1047
pub fn get_many ( & self , refs : & [ObjectRef]) -> Vec< Result< Object> > {
1048
use rayon:: prelude:: * ;
1049
let paths: Vec< ( ObjectRef, PathBuf) > =
1050
refs.iter ( ) .map ( | r | ( * r, self .path_for ( r) ) ) .collect ( ) ;
1051
let mut found: Vec< Option< Result< Object> > > =
1052
paths.par_iter ( ) .map ( | ( r , p ) | read_loose_at ( p, r) ) .collect ( ) ;
1053
1054
let packs = self .packs.borrow ( ) ;
1055
let packed: Vec< ( usize , usize , PackEntry) > = found
75167672 Andy McCall 1 month ago
1056
.iter ( )
afaba5f1 Andy McCall 1 month ago ↶
1057
.enumerate ( )
1058
.filter ( | ( _, f ) | f.is_none ( ) )
1059
.filter_map ( | ( i , _) | {
1060
packs
1061
.iter ( )
1062
.enumerate ( )
1063
.find_map ( | ( p , pack ) | pack.find ( & refs[ i] ) .map ( | e | ( i, p, e) ) )
1064
} )
1065
.collect ( ) ;
1066
let indexes: & [ PackIndex] = & packs;
1067
let read: Vec< ( usize , Result< Object> ) > = packed
1068
.par_iter ( )
1069
.map ( | ( i , p , e ) | ( * i, indexes[ * p] .read ( & refs[ * i] , * e) ) )
1070
.collect ( ) ;
1071
drop ( packs) ;
1072
for ( i, r) in read {
1073
found[ i] = Some ( r) ;
ff0af1ee unknown 1 month ago
1074
}
afaba5f1 Andy McCall 1 month ago ↶
1075
found
1076
.into_iter ( )
1077
.zip ( refs)
1078
.map ( | ( f , r ) | match f {
1079
Some ( f) => f,
1080
None => self .get ( r) ,
1081
} )
1082
.collect ( )
ff0af1ee unknown 1 month ago
1083
}
1084
1085
pub fn get_typed ( & self , r : & ObjectRef, want : ObjectType) -> Result< Vec< u8 > > {
1086
let ( ty, body) = self .get ( r) ? ;
1087
if ty ! = want {
1088
return Err ( StoreError:: WrongType( * r, ty.name ( ) , want.name ( ) ) ) ;
1089
}
1090
Ok ( body)
1091
}
1092
6f8aeb99 Andy McCall 1 month ago ↶
1093
1094
ff0af1ee unknown 1 month ago
1095
pub fn put_change ( & self , c : & Change) -> Result< ObjectRef> {
17f94b13 Andy McCall 1 month ago ↶
1096
let bytes = c.encode_streaming ( | batch | self .put_texts ( & batch) ) ? ;
1097
self .put ( ObjectType:: Change, & bytes)
6f8aeb99 Andy McCall 1 month ago ↶
1098
}
1099
17f94b13 Andy McCall 1 month ago ↶
1100
1101
1102
pub fn put_texts ( & self , texts : & [crate:: objects:: StoredText]) -> Result< ( ) > {
192fad1e Andy McCall 1 month ago ↶
1103
let mut chunks: Vec< ( ObjectType, & [u8 ]) > = Vec :: new( ) ;
1104
for t in texts {
1105
if ! self .has ( & t.blob.hash ( ) ) {
1106
chunks.extend (
1107
crate :: chunk:: chunks( & t.content)
1108
.into_iter ( )
1109
.map ( | c | ( ObjectType:: Chunk, c) ) ,
1110
) ;
1111
}
6f8aeb99 Andy McCall 1 month ago ↶
1112
}
192fad1e Andy McCall 1 month ago ↶
1113
self .put_many ( & chunks) ? ;
1114
for t in texts {
1115
self .put ( ObjectType:: Blob, & t.blob.encode ( ) ) ? ;
6f8aeb99 Andy McCall 1 month ago ↶
1116
}
17f94b13 Andy McCall 1 month ago ↶
1117
Ok ( ( ) )
ff0af1ee unknown 1 month ago
1118
}
1119
1120
pub fn get_change ( & self , r : & ObjectRef) -> Result< Change> {
6f8aeb99 Andy McCall 1 month ago ↶
1121
Ok ( self .get_change_parts ( r) ? .0 )
1122
}
1123
1124
1125
1126
pub fn get_change_parts ( & self , r : & ObjectRef) -> Result< ( Change, Vec< ObjectRef> ) > {
1127
let bytes = self .get_typed ( r, ObjectType:: Change) ? ;
1128
let mut parts = Vec :: new( ) ;
1129
let mut failed = None ;
0f93f49c Andy McCall 1 month ago ↶
1130
let decoded = Change:: decode_with( & bytes, & mut | text, count, form| match self
1131
.read_text ( text)
1132
{
6f8aeb99 Andy McCall 1 month ago ↶
1133
Ok ( ( blob, content) ) => {
1134
parts.push ( * text) ;
1135
parts.extend ( blob.chunks) ;
0f93f49c Andy McCall 1 month ago ↶
1136
crate :: objects:: text_lines( & content, count, form)
6f8aeb99 Andy McCall 1 month ago ↶
1137
}
1138
Err ( e) => {
1139
let why = e.to_string ( ) ;
1140
failed = Some ( e) ;
1141
Err ( ObjectError:: Schema( format! (
1142
" text blob {} : {why} " ,
1143
text.short ( 12 )
1144
) ) )
1145
}
1146
} ) ;
1147
match ( decoded, failed) {
1148
( Ok ( c) , _ ) => Ok ( ( c, parts) ) ,
1149
( Err ( _ ) , Some ( StoreError:: NotFound( t) ) ) => Err ( StoreError:: Corrupt(
1150
* r,
1151
format! ( " its text blob {} is missing" , t.short ( 12 ) ) ,
1152
) ) ,
1153
( Err ( e) , _ ) => Err ( StoreError:: Corrupt( * r, e.to_string ( ) ) ) ,
1154
}
1155
}
1156
a4fad5b0 Andy McCall 1 month ago ↶
1157
1158
1159
1160
1161
pub fn get_change_lazy ( & self , r : & ObjectRef) -> Result< ( Change, Vec< crate:: objects:: TextRef> ) > {
86e1824f Andy McCall 1 month ago ↶
1162
let bytes = self .get_typed ( r, ObjectType:: Change) ? ;
a4fad5b0 Andy McCall 1 month ago ↶
1163
Change:: decode_skeleton( & bytes, * r) .map_err ( | e | StoreError:: Corrupt( * r, e.to_string ( ) ) )
86e1824f Andy McCall 1 month ago ↶
1164
}
1165
69816d68 Andy McCall 1 month ago ↶
1166
1167
1168
ce14f0d6 Andy McCall 1 month ago ↶
1169
1170
1171
1172
69816d68 Andy McCall 1 month ago ↶
1173
pub fn get_change_meta ( & self , r : & ObjectRef) -> Result< crate:: state:: ChangeMeta> {
ce14f0d6 Andy McCall 1 month ago ↶
1174
if self .metas.borrow ( ) .is_none ( ) {
1175
* self .metas.borrow_mut ( ) = Some ( self .read_metas_cache ( ) ) ;
1176
}
1177
let cached = self .metas.borrow ( ) .as_ref ( ) .and_then ( | m | m.get ( r) .cloned ( ) ) ;
1178
if let Some ( meta) = cached {
1179
if self .has ( r) {
1180
# [ cfg ( test ) ]
1181
assert_eq! (
1182
meta,
1183
Change:: decode_meta( & self .get_typed ( r, ObjectType:: Change) ? ) .unwrap ( ) ,
1184
" the metadata cache holds a change's metadata"
1185
) ;
1186
return Ok ( meta) ;
1187
}
1188
}
4d6c9c1b Andy McCall 1 month ago ↶
1189
let bytes = self .get_typed ( r, ObjectType:: Change) ? ;
ce14f0d6 Andy McCall 1 month ago ↶
1190
let meta =
1191
Change:: decode_meta( & bytes) .map_err ( | e | StoreError:: Corrupt( * r, e.to_string ( ) ) ) ? ;
1192
self .remember_meta ( r, & meta) ;
1193
Ok ( meta)
1194
}
1195
1196
1197
1198
1199
1200
fn read_metas_cache ( & self ) -> crate:: fasthash:: FastMap< ObjectRef, crate:: state:: ChangeMeta> {
1201
let mut out = crate :: fasthash:: FastMap:: default( ) ;
1202
let Ok ( bytes) = fs:: read( self .dir.join ( " metas" ) ) else {
1203
return out;
1204
} ;
1205
let mut r = crate :: flat:: Reader:: new( & bytes) ;
1206
let mut good = 0 ;
1207
while ! r.at_end ( ) {
1208
let mut record = | | -> std:: result:: Result< _ , ObjectError> {
1209
let h = r.object ( ) ? ;
1210
let body = r.bytes ( ) ? ;
1211
let check = r.fixed:: < 8> ( ) ? ;
1212
Ok ( ( h, body, check) )
1213
} ;
1214
let Ok ( ( h, body, check) ) = record ( ) else {
1215
break ;
1216
} ;
1217
let mut sum = blake3:: Hasher:: new( ) ;
1218
sum.update ( & h.0 ) .update ( body) ;
1219
if sum.finalize ( ) .as_bytes ( ) [ .. 8 ] ! = check {
1220
break ;
1221
}
1222
match crate :: state:: ChangeMeta:: read_flat( & mut crate :: flat:: Reader:: new( body) ) {
1223
Ok ( m) => out.insert ( h, m) ,
1224
Err ( _ ) => break ,
1225
} ;
1226
good + = 32 + 4 + body.len ( ) + 8 ;
1227
}
1228
1229
if good < bytes.len ( ) {
1230
let _ = fs:: OpenOptions:: new( )
1231
.write ( true )
1232
.open ( self .dir.join ( " metas" ) )
1233
.and_then ( | f | f.set_len ( good as u64 ) ) ;
1234
}
1235
out
1236
}
1237
1238
1239
1240
fn remember_meta ( & self , r : & ObjectRef, meta : & crate:: state:: ChangeMeta) {
1241
let mut body = crate :: flat:: Writer:: default( ) ;
1242
meta.write_flat ( & mut body) ;
1243
let mut w = crate :: flat:: Writer:: default( ) ;
1244
w.object ( r) ;
1245
w.bytes ( & body.0 ) ;
1246
let mut sum = blake3:: Hasher:: new( ) ;
1247
sum.update ( & r.0 ) .update ( & body.0 ) ;
1248
w.fixed ( & sum.finalize ( ) .as_bytes ( ) [ .. 8 ] ) ;
1249
let _ = fs:: OpenOptions:: new( )
1250
.create ( true )
1251
.append ( true )
1252
.open ( self .dir.join ( " metas" ) )
1253
.and_then ( | mut f | f.write_all ( & w.0 ) ) ;
1254
if let Some ( m) = self .metas.borrow_mut ( ) .as_mut ( ) {
1255
m.insert ( * r, meta.clone ( ) ) ;
1256
}
69816d68 Andy McCall 1 month ago ↶
1257
}
1258
6f8aeb99 Andy McCall 1 month ago ↶
1259
fn read_text ( & self , r : & ObjectRef) -> Result< ( Blob, Vec< u8 > ) > {
1260
let blob = Blob:: decode( & self .get_typed ( r, ObjectType:: Blob) ? ) ? ;
1261
let mut content = Vec :: with_capacity( blob.length as usize ) ;
1262
for c in & blob.chunks {
1263
content.extend ( self .get_typed ( c, ObjectType:: Chunk) ? ) ;
1264
}
1265
Ok ( ( blob, content) )
ff0af1ee unknown 1 month ago
1266
}
1267
1268
pub fn put_chunk ( & self , bytes : & [u8 ]) -> Result< ObjectRef> {
1269
self .put ( ObjectType:: Chunk, bytes)
1270
}
1271
cdb5beba Andy McCall 1 month ago ↶
1272
1273
pub fn put_chunk_nocompress ( & self , bytes : & [u8 ]) -> Result< ObjectRef> {
8f10a07a Andy McCall 1 month ago ↶
1274
let r = hash_object ( ObjectType:: Chunk, bytes) ;
cdb5beba Andy McCall 1 month ago ↶
1275
let dir = self .dir.join ( " nocompress" ) ;
1276
fs:: create_dir_all( & dir) ? ;
1277
let _ = fs:: File:: create( dir.join ( r.to_hex ( ) ) ) ;
8f10a07a Andy McCall 1 month ago ↶
1278
self .put ( ObjectType:: Chunk, bytes)
cdb5beba Andy McCall 1 month ago ↶
1279
}
1280
1281
fn is_nocompress ( & self , r : & ObjectRef) -> bool {
1282
self .dir.join ( " nocompress" ) .join ( r.to_hex ( ) ) .exists ( )
1283
}
1284
86e682c2 Andy McCall 1 month ago ↶
1285
pub fn put_state ( & self , st : & StateObject) -> Result< ObjectRef> {
1286
self .put ( ObjectType:: State, & st.encode ( ) )
1287
}
1288
1289
pub fn get_state ( & self , r : & ObjectRef) -> Result< StateObject> {
1290
Ok ( StateObject:: decode( & self .get_typed ( r, ObjectType:: State) ? ) ? )
1291
}
1292
5b9c6ad4 Andy McCall 1 month ago ↶
1293
pub fn put_attest ( & self , a : & Attest) -> Result< ObjectRef> {
1294
self .put ( ObjectType:: Attest, & a.encode ( ) )
1295
}
1296
1297
pub fn get_attest ( & self , r : & ObjectRef) -> Result< Attest> {
1298
Ok ( Attest:: decode( & self .get_typed ( r, ObjectType:: Attest) ? ) ? )
fc1a466a Andy McCall 1 month ago ↶
1299
}
1300
1301
pub fn put_receipt ( & self , r : & Receipt) -> Result< ObjectRef> {
1302
self .put ( ObjectType:: Receipt, & r.encode ( ) )
5b9c6ad4 Andy McCall 1 month ago ↶
1303
}
1304
fc1a466a Andy McCall 1 month ago ↶
1305
pub fn get_receipt ( & self , r : & ObjectRef) -> Result< Receipt> {
1306
Ok ( Receipt:: decode( & self .get_typed ( r, ObjectType:: Receipt) ? ) ? )
5b9c6ad4 Andy McCall 1 month ago ↶
1307
}
1308
fc1a466a Andy McCall 1 month ago ↶
1309
fn index_path ( & self , index : & str , key : & ObjectRef) -> PathBuf {
1310
let h = key.to_hex ( ) ;
1311
self .dir.join ( index) .join ( & h[ .. 2 ] ) .join ( & h[ 2 .. ] )
1312
}
1313
1314
1315
fn index_add ( & self , index : & str , key : & ObjectRef, value : & ObjectRef) -> Result< ( ) > {
1316
let mut values = self .index_get ( index, key) ? ;
1317
if values.contains ( value) {
5b9c6ad4 Andy McCall 1 month ago ↶
1318
return Ok ( ( ) ) ;
1319
}
fc1a466a Andy McCall 1 month ago ↶
1320
values.push ( * value) ;
1321
values.sort ( ) ;
1322
let path = self .index_path ( index, key) ;
5b9c6ad4 Andy McCall 1 month ago ↶
1323
fs:: create_dir_all( path.parent ( ) .unwrap ( ) ) ? ;
fc1a466a Andy McCall 1 month ago ↶
1324
let text: String = values.iter ( ) .map ( | s | format! ( " {} \n " , s.to_hex ( ) ) ) .collect ( ) ;
4dcb8c09 Andy McCall 1 month ago ↶
1325
crate :: durable:: write_atomic( & path, text.as_bytes ( ) ) ? ;
5b9c6ad4 Andy McCall 1 month ago ↶
1326
Ok ( ( ) )
1327
}
1328
fc1a466a Andy McCall 1 month ago ↶
1329
fn index_get ( & self , index : & str , key : & ObjectRef) -> Result< Vec< ObjectRef> > {
1330
match fs:: read_to_string( self .index_path ( index, key) ) {
5b9c6ad4 Andy McCall 1 month ago ↶
1331
Ok ( t) => Ok ( t.lines ( ) .filter_map ( ObjectRef:: from_hex) .collect ( ) ) ,
1332
Err ( e) if e.kind ( ) = = io:: ErrorKind:: NotFound => Ok ( vec! [ ] ) ,
1333
Err ( e) => Err ( e.into ( ) ) ,
1334
}
fc1a466a Andy McCall 1 month ago ↶
1335
}
1336
1337
1338
pub fn index_seal ( & self , change : & ObjectRef, attest : & ObjectRef) -> Result< ( ) > {
1339
self .index_add ( " seals" , change, attest)
1340
}
1341
1342
1343
pub fn seals_for ( & self , change : & ObjectRef) -> Result< Vec< ObjectRef> > {
1344
self .index_get ( " seals" , change)
1345
}
1346
1347
1348
pub fn index_receipt ( & self , state : & ObjectRef, attest : & ObjectRef) -> Result< ( ) > {
1349
self .index_add ( " receipts" , state, attest)
1350
}
1351
1352
1353
pub fn receipts_for ( & self , state : & ObjectRef) -> Result< Vec< ObjectRef> > {
1354
self .index_get ( " receipts" , state)
1355
}
1356
1357
1358
pub fn receipt_states ( & self ) -> Result< Vec< ObjectRef> > {
1359
let mut out = Vec :: new( ) ;
1360
let dir = self .dir.join ( " receipts" ) ;
1361
let top = match fs:: read_dir( & dir) {
1362
Ok ( d) => d,
1363
Err ( e) if e.kind ( ) = = io:: ErrorKind:: NotFound => return Ok ( out) ,
1364
Err ( e) => return Err ( e.into ( ) ) ,
1365
} ;
1366
for d in top {
1367
let d = d? ;
1368
let prefix = d.file_name ( ) .to_string_lossy ( ) .into_owned ( ) ;
1369
for f in fs:: read_dir( d.path ( ) ) ? {
1370
let name = f? .file_name ( ) .to_string_lossy ( ) .into_owned ( ) ;
1371
if let Some ( r) = ObjectRef:: from_hex( & format! ( " {prefix} {name} " ) ) {
1372
out.push ( r) ;
1373
}
1374
}
1375
}
1376
out.sort ( ) ;
1377
Ok ( out)
5b9c6ad4 Andy McCall 1 month ago ↶
1378
}
1379
1983686f Andy McCall 1 month ago ↶
1380
pub fn put_identity ( & self , id : & Identity) -> Result< ObjectRef> {
1381
self .put ( ObjectType:: Identity, & id.encode ( ) )
1382
}
1383
1384
pub fn get_identity ( & self , r : & ObjectRef) -> Result< Identity> {
1385
Ok ( Identity:: decode( & self .get_typed ( r, ObjectType:: Identity) ? ) ? )
1386
}
1387
1388
fn sig_path ( & self , r : & ObjectRef) -> PathBuf {
1389
let h = r.to_hex ( ) ;
1390
self .dir.join ( " sigs" ) .join ( & h[ .. 2 ] ) .join ( & h[ 2 .. ] )
1391
}
1392
1393
1394
pub fn signatures ( & self , r : & ObjectRef) -> Result< Vec< Signature> > {
1395
match fs:: read( self .sig_path ( r) ) {
1396
Ok ( b) => Ok ( Signature:: decode_list( & b) ? ) ,
1397
Err ( e) if e.kind ( ) = = io:: ErrorKind:: NotFound => Ok ( vec! [ ] ) ,
1398
Err ( e) => Err ( e.into ( ) ) ,
1399
}
1400
}
1401
1402
1403
pub fn put_signature ( & self , r : & ObjectRef, sig : & Signature) -> Result< ( ) > {
1404
let mut sigs = self .signatures ( r) ? ;
1405
sigs.retain ( | s | s.key ! = sig.key ) ;
1406
sigs.push ( sig.clone ( ) ) ;
5b1114a0 Andy McCall 1 month ago ↶
1407
sigs.sort_by_key ( | s | s.key ) ;
1983686f Andy McCall 1 month ago ↶
1408
let path = self .sig_path ( r) ;
1409
fs:: create_dir_all( path.parent ( ) .unwrap ( ) ) ? ;
1410
let mut nonce = [ 0 u8 ; 8 ] ;
1411
rand:: rngs:: OsRng.fill_bytes ( & mut nonce) ;
1412
let tmp = self .dir.join ( format! ( " sigs-tmp-{} " , hex:: encode( nonce) ) ) ;
1413
{
1414
let mut f = fs:: File:: create( & tmp) ? ;
1415
f.write_all ( & Signature:: encode_list( & sigs) ) ? ;
1416
f.sync_all ( ) ? ;
1417
}
4dcb8c09 Andy McCall 1 month ago ↶
1418
crate :: durable:: rename( & tmp, & path) ? ;
1983686f Andy McCall 1 month ago ↶
1419
Ok ( ( ) )
1420
}
1421
d02e545d Andy McCall 1 month ago ↶
1422
ff0af1ee unknown 1 month ago
1423
pub fn list ( & self ) -> io:: Result< Vec< ObjectRef> > {
d02e545d Andy McCall 1 month ago ↶
1424
let mut out: Vec< ObjectRef> = self .loose_objects ( ) ? .into_iter ( ) .map ( | ( r , _) | r ) .collect ( ) ;
1425
for p in self .packs.borrow ( ) .iter ( ) {
1426
out.extend ( p.refs.iter ( ) .copied ( ) ) ;
ff0af1ee unknown 1 month ago
1427
}
1428
out.sort ( ) ;
d02e545d Andy McCall 1 month ago ↶
1429
out.dedup ( ) ;
ff0af1ee unknown 1 month ago
1430
Ok ( out)
1431
}
afaba5f1 Andy McCall 1 month ago ↶
1432
}
1433
1434
1435
1436
fn read_loose_at ( path : & Path, r : & ObjectRef) -> Option< Result< Object> > {
1437
let data = match fs:: read( path) {
1438
Ok ( d) => d,
1439
Err ( e) if e.kind ( ) = = io:: ErrorKind:: NotFound => return None ,
1440
Err ( e) => return Some ( Err ( e.into ( ) ) ) ,
1441
} ;
1442
Some ( ( | | {
1443
let nul = data
1444
.iter ( )
1445
.position ( | & b | b = = 0 )
1446
.ok_or_else ( | | StoreError:: Corrupt( * r, " no header" .into ( ) ) ) ? ;
1447
let head = std:: str:: from_utf8( & data[ .. nul] )
1448
.map_err ( | _| StoreError:: Corrupt( * r, " header not UTF-8" .into ( ) ) ) ? ;
1449
let mut parts = head.split ( ' ' ) ;
1450
let ( Some ( " oxy1" ) , Some ( ty) , Some ( len) , None ) =
1451
( parts.next ( ) , parts.next ( ) , parts.next ( ) , parts.next ( ) )
1452
else {
1453
return Err ( StoreError:: Corrupt( * r, format! ( " bad header {head:?} " ) ) ) ;
1454
} ;
1455
let ty = ObjectType:: from_name( ty)
1456
.ok_or_else ( | | StoreError:: Corrupt( * r, format! ( " unknown type {ty} " ) ) ) ? ;
1457
let len: usize = len
1458
.parse ( )
1459
.map_err ( | _| StoreError:: Corrupt( * r, " bad length" .into ( ) ) ) ? ;
1460
let body = & data[ nul + 1 .. ] ;
1461
if body.len ( ) ! = len {
1462
return Err ( StoreError:: Corrupt(
1463
* r,
1464
format! ( " length {} != header {len} " , body.len ( ) ) ,
1465
) ) ;
1466
}
1467
if hash_object ( ty, body) ! = * r {
1468
return Err ( StoreError:: Corrupt( * r, " hash mismatch" .into ( ) ) ) ;
1469
}
1470
Ok ( ( ty, body.to_vec ( ) ) )
1471
} ) ( ) )
ff0af1ee unknown 1 month ago
1472
}
1473
1474
# [ cfg ( test ) ]
1475
mod tests {
1476
use super :: * ;
1477
1478
# [ test ]
1479
fn put_get_verify ( ) {
1480
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1481
let s = Store:: open( dir.path ( ) .join ( " objects" ) ) .unwrap ( ) ;
1482
let r = s.put ( ObjectType:: Chunk, b " hello" ) .unwrap ( ) ;
1483
assert! ( s.has ( & r) ) ;
1484
assert_eq! ( s.get ( & r) .unwrap ( ) , ( ObjectType:: Chunk, b " hello" .to_vec ( ) ) ) ;
1485
assert_eq! ( s.put ( ObjectType:: Chunk, b " hello" ) .unwrap ( ) , r) ;
1486
assert_eq! ( s.list ( ) .unwrap ( ) , vec! [ r] ) ;
75167672 Andy McCall 1 month ago
1487
assert! ( matches! (
1488
s.get_typed ( & r, ObjectType:: Blob) ,
1489
Err ( StoreError:: WrongType( .. ) )
1490
) ) ;
1491
assert! ( matches! (
1492
s.get ( & ObjectRef( [ 9 ; 32 ] ) ) ,
1493
Err ( StoreError:: NotFound( _ ) )
1494
) ) ;
ff0af1ee unknown 1 month ago
1495
1496
1497
let path = s.path_for ( & r) ;
1498
let mut data = fs:: read( & path) .unwrap ( ) ;
1499
let last = data.len ( ) - 1 ;
1500
data[ last] ^ = 1 ;
1501
fs:: write( & path, data) .unwrap ( ) ;
1502
assert! ( matches! ( s.get ( & r) , Err ( StoreError:: Corrupt( .. ) ) ) ) ;
1503
}
1504
1505
# [ test ]
d02e545d Andy McCall 1 month ago ↶
1506
fn packing_keeps_every_object_readable ( ) {
1507
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1508
let s = Store:: open( dir.path ( ) ) .unwrap ( ) ;
1509
let mut refs = Vec :: new( ) ;
1510
for i in 0 .. 300 u32 {
1511
let body = format! ( " object {i} " ) .repeat ( 1 + ( i % 7 ) as usize ) ;
1512
refs.push ( ( s.put ( ObjectType:: Chunk, body.as_bytes ( ) ) .unwrap ( ) , body) ) ;
1513
}
1514
let c = crate :: objects:: tests:: sample_change( ) ;
1515
let cr = s.put_change ( & c) .unwrap ( ) ;
1516
assert_eq! ( s.loose_count ( ) .unwrap ( ) , 301 ) ;
1517
let report = s.pack_loose ( ) .unwrap ( ) ;
1518
assert_eq! ( report.objects, 301 ) ;
1519
assert! ( report.packed_bytes < report.raw_bytes, " {report:?}" ) ;
1520
assert_eq! ( s.loose_count ( ) .unwrap ( ) , 0 ) ;
1521
assert_eq! ( s.pack_count ( ) , 1 ) ;
1522
for ( r, body) in & refs {
1523
assert! ( s.has ( r) ) ;
75167672 Andy McCall 1 month ago
1524
assert_eq! (
1525
s.get ( r) .unwrap ( ) ,
1526
( ObjectType:: Chunk, body.as_bytes ( ) .to_vec ( ) )
1527
) ;
d02e545d Andy McCall 1 month ago ↶
1528
}
1529
assert_eq! ( s.get_change ( & cr) .unwrap ( ) , c) ;
1530
assert_eq! ( s.list ( ) .unwrap ( ) .len ( ) , 301 ) ;
1531
1532
75167672 Andy McCall 1 month ago
1533
assert_eq! (
1534
s.put ( ObjectType:: Chunk, refs[ 0 ] .1. as_bytes ( ) ) .unwrap ( ) ,
1535
refs[ 0 ] .0
1536
) ;
d02e545d Andy McCall 1 month ago ↶
1537
assert_eq! ( s.loose_count ( ) .unwrap ( ) , 0 ) ;
1538
let extra = s.put ( ObjectType:: Chunk, b " after packing" ) .unwrap ( ) ;
1539
s.pack_loose ( ) .unwrap ( ) ;
1540
assert_eq! ( s.pack_count ( ) , 2 ) ;
1541
assert_eq! ( s.get ( & extra) .unwrap ( ) .1 , b " after packing" ) ;
1542
1543
let s2 = Store:: open( dir.path ( ) ) .unwrap ( ) ;
1544
assert_eq! ( s2.pack_count ( ) , 2 ) ;
1545
assert_eq! ( s2.get ( & refs[ 5 ] .0 ) .unwrap ( ) .1 , refs[ 5 ] .1. as_bytes ( ) ) ;
75167672 Andy McCall 1 month ago
1546
assert! ( matches! (
1547
s2.get ( & ObjectRef( [ 9 ; 32 ] ) ) ,
1548
Err ( StoreError:: NotFound( _ ) )
1549
) ) ;
d02e545d Andy McCall 1 month ago ↶
1550
1551
let target = refs[ 5 ] .0 ;
1552
let ( pack_path, offset) = {
1553
let packs = s2.packs.borrow ( ) ;
1554
let ( i, e) = s2.find_packed ( & target) .unwrap ( ) ;
1555
( packs[ i] .path.clone ( ) , e.offset as usize )
1556
} ;
1557
let mut data = fs:: read( & pack_path) .unwrap ( ) ;
1558
data[ offset] ^ = 0xff ;
1559
fs:: write( & pack_path, data) .unwrap ( ) ;
1560
let s3 = Store:: open( dir.path ( ) ) .unwrap ( ) ;
1561
assert! ( matches! ( s3.get ( & target) , Err ( StoreError:: Corrupt( .. ) ) ) ) ;
75167672 Andy McCall 1 month ago
1562
assert_eq! (
1563
s3.get ( & refs[ 6 ] .0 ) .unwrap ( ) .1 ,
1564
refs[ 6 ] .1. as_bytes ( ) ,
1565
" other objects are unaffected"
1566
) ;
d02e545d Andy McCall 1 month ago ↶
1567
}
1568
1569
# [ test ]
cdb5beba Andy McCall 1 month ago ↶
1570
fn nocompress_chunks_pack_raw ( ) {
1571
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1572
let s = Store:: open( dir.path ( ) ) .unwrap ( ) ;
1573
let body = vec! [ b ' a' ; 10_000 ] ;
1574
let raw = s.put_chunk_nocompress ( & body) .unwrap ( ) ;
1575
let compressible = s.put ( ObjectType:: Chunk, & vec! [ b ' b' ; 10_000 ] ) .unwrap ( ) ;
1576
s.pack_loose ( ) .unwrap ( ) ;
1577
let packs = s.packs.borrow ( ) ;
1578
let ( i, e) = s.find_packed ( & raw) .unwrap ( ) ;
1579
assert_eq! ( ( e.flags, e.stored_len as usize ) , ( 0 , 10_000 ) ) ;
1580
let ( _ , c) = s.find_packed ( & compressible) .unwrap ( ) ;
1581
assert_eq! ( c.flags, FLAG_ZSTD ) ;
1582
assert! ( c.stored_len < 10_000 ) ;
1583
assert_eq! ( packs[ i] .read ( & raw, e) .unwrap ( ) .1 , body) ;
1584
}
1585
1586
# [ test ]
135212de Andy McCall 1 month ago ↶
1587
fn many_packs_merge_into_one ( ) {
1588
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1589
let s = Store:: open( dir.path ( ) ) .unwrap ( ) ;
1590
let mut refs = Vec :: new( ) ;
1591
for i in 0 .. ( MAX_PACKS + 2 ) {
75167672 Andy McCall 1 month ago
1592
refs.push (
1593
s.put ( ObjectType:: Chunk, format! ( " pack {i} " ) .as_bytes ( ) )
1594
.unwrap ( ) ,
1595
) ;
135212de Andy McCall 1 month ago ↶
1596
s.pack_loose ( ) .unwrap ( ) ;
1597
}
1598
assert_eq! ( s.pack_count ( ) , MAX_PACKS + 2 ) ;
1599
assert_eq! ( s.merge_packs ( ) .unwrap ( ) , MAX_PACKS + 2 ) ;
1600
assert_eq! ( s.pack_count ( ) , 1 ) ;
1601
assert_eq! ( s.merge_packs ( ) .unwrap ( ) , 0 ) ;
1602
for ( i, r) in refs.iter ( ) .enumerate ( ) {
1603
assert_eq! ( s.get ( r) .unwrap ( ) .1 , format! ( " pack {i} " ) .as_bytes ( ) ) ;
1604
}
1605
assert_eq! ( Store:: open( dir.path ( ) ) .unwrap ( ) .pack_count ( ) , 1 ) ;
1606
}
1607
1608
# [ test ]
ba160f60 Andy McCall 1 month ago ↶
1609
fn prune_removes_unreachable_loose_and_packed_objects ( ) {
1610
use std:: collections:: HashSet;
1611
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1612
let s = Store:: open( dir.path ( ) ) .unwrap ( ) ;
1613
let keep_a = s.put ( ObjectType:: Chunk, b " keep a" ) .unwrap ( ) ;
1614
let drop_a = s.put ( ObjectType:: Chunk, b " drop a" ) .unwrap ( ) ;
1615
s.pack_loose ( ) .unwrap ( ) ;
1616
let keep_b = s.put ( ObjectType:: Chunk, b " keep b" ) .unwrap ( ) ;
1617
let drop_b = s.put ( ObjectType:: Chunk, b " drop b" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1618
let sk = crate :: identity:: SecretKey {
1619
ed_seed: [ 1 ; 32 ] ,
1620
ml_seed: [ 2 ; 32 ] ,
1621
} ;
ba160f60 Andy McCall 1 month ago ↶
1622
let id = sk.identity ( " a" , " b" , 0 ) ;
1623
let key = s.put_identity ( & id) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1624
s.put_signature (
1625
& drop_b,
1626
& sk.sign ( crate :: identity:: Domain:: Change, & key, & drop_b) ,
1627
)
1628
.unwrap ( ) ;
ba160f60 Andy McCall 1 month ago ↶
1629
s.index_seal ( & drop_a, & ObjectRef( [ 3 ; 32 ] ) ) .unwrap ( ) ;
1630
let keep: HashSet< ObjectRef> = [ keep_a, keep_b, key] .into_iter ( ) .collect ( ) ;
1631
75167672 Andy McCall 1 month ago
1632
let r = s
1633
.prune ( & keep, std:: time:: Duration:: from_secs( 3600 ) )
1634
.unwrap ( ) ;
ba160f60 Andy McCall 1 month ago ↶
1635
assert_eq! ( ( r.objects, r.deferred) , ( 0 , 2 ) ) ;
1636
let r = s.prune ( & keep, std:: time:: Duration:: ZERO ) .unwrap ( ) ;
1637
assert_eq! ( r.objects, 2 , " {r:?}" ) ;
1638
assert! ( s.has ( & keep_a) & & s.has ( & keep_b) & & s.has ( & key) ) ;
1639
assert! ( ! s.has ( & drop_a) & & ! s.has ( & drop_b) ) ;
1640
assert! ( s.signatures ( & drop_b) .unwrap ( ) .is_empty ( ) ) ;
1641
assert! ( s.seals_for ( & drop_a) .unwrap ( ) .is_empty ( ) ) ;
1642
assert_eq! ( s.pack_count ( ) , 1 ) ;
1643
assert_eq! ( s.get ( & keep_a) .unwrap ( ) .1 , b " keep a" ) ;
1644
assert_eq! ( s.list ( ) .unwrap ( ) .len ( ) , 3 ) ;
1645
}
1646
1647
# [ test ]
1983686f Andy McCall 1 month ago ↶
1648
fn signatures_are_stored_beside_objects ( ) {
1649
use crate :: identity:: { Domain, SecretKey} ;
1650
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1651
let s = Store:: open( dir.path ( ) ) .unwrap ( ) ;
1652
let r = s.put ( ObjectType:: Chunk, b " x" ) .unwrap ( ) ;
1653
assert! ( s.signatures ( & r) .unwrap ( ) .is_empty ( ) ) ;
75167672 Andy McCall 1 month ago
1654
let sk = SecretKey {
1655
ed_seed: [ 1 ; 32 ] ,
1656
ml_seed: [ 2 ; 32 ] ,
1657
} ;
1983686f Andy McCall 1 month ago ↶
1658
let id = sk.identity ( " a" , " b" , 0 ) ;
1659
let key = s.put_identity ( & id) .unwrap ( ) ;
1660
assert_eq! ( s.get_identity ( & key) .unwrap ( ) , id) ;
1661
let sig = sk.sign ( Domain:: Change, & key, & r) ;
1662
s.put_signature ( & r, & sig) .unwrap ( ) ;
1663
s.put_signature ( & r, & sig) .unwrap ( ) ;
1664
assert_eq! ( s.signatures ( & r) .unwrap ( ) , vec! [ sig] ) ;
1665
1666
assert_eq! ( s.get ( & r) .unwrap ( ) .1 , b " x" ) ;
1667
}
1668
1669
# [ test ]
ff0af1ee unknown 1 month ago
1670
fn change_round_trip_through_store ( ) {
1671
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1672
let s = Store:: open( dir.path ( ) ) .unwrap ( ) ;
1673
let c = crate :: objects:: tests:: sample_change( ) ;
1674
let r = s.put_change ( & c) .unwrap ( ) ;
1675
assert_eq! ( r, c.hash ( ) ) ;
1676
assert_eq! ( s.get_change ( & r) .unwrap ( ) , c) ;
1677
}
2b872d07 Andy McCall 1 month ago ↶
1678
1679
fn xorshift ( state : & mut u64 ) -> u64 {
1680
* state ^ = * state < < 13 ;
1681
* state ^ = * state > > 7 ;
1682
* state ^ = * state < < 17 ;
1683
* state
1684
}
1685
1686
1687
1688
1689
# [ test ]
1690
fn corrupted_packs_and_indexes_fail_cleanly ( ) {
1691
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1692
let objects = dir.path ( ) .join ( " objects" ) ;
1693
let s = Store:: open( & objects) .unwrap ( ) ;
1694
let mut refs = Vec :: new( ) ;
1695
let mut seed = 0x9e3779b97f4a7c15 u64 ;
1696
for i in 0 .. 40 u32 {
1697
let len = ( xorshift ( & mut seed) % 5000 ) as usize ;
1698
let mut body = vec! [ 0 u8 ; len] ;
1699
for b in body.iter_mut ( ) {
75167672 Andy McCall 1 month ago
1700
* b = if i % 2 = = 0 {
1701
( xorshift ( & mut seed) & 0xff ) as u8
1702
} else {
1703
b ' a' + ( i % 26 ) as u8
1704
} ;
2b872d07 Andy McCall 1 month ago ↶
1705
}
1706
refs.push ( s.put ( ObjectType:: Chunk, & body) .unwrap ( ) ) ;
1707
}
1708
s.pack_loose ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1709
let pack = fs:: read_dir( & objects)
1710
.unwrap ( )
1711
.filter_map ( | e | e.ok ( ) .map ( | e | e.path ( ) ) )
1712
.find ( | p | p.extension ( ) .is_some_and ( | x | x = = " pack" ) )
1713
.unwrap ( ) ;
2b872d07 Andy McCall 1 month ago ↶
1714
let idx = pack.with_extension ( " idx" ) ;
1715
let pristine_pack = fs:: read( & pack) .unwrap ( ) ;
1716
let pristine_idx = fs:: read( & idx) .unwrap ( ) ;
1717
1718
for round in 0 .. 300 u32 {
75167672 Andy McCall 1 month ago
1719
let ( path, pristine) = if round % 2 = = 0 {
1720
( & pack, & pristine_pack)
1721
} else {
1722
( & idx, & pristine_idx)
1723
} ;
2b872d07 Andy McCall 1 month ago ↶
1724
let mut data = pristine.clone ( ) ;
1725
match xorshift ( & mut seed) % 4 {
1726
0 => {
1727
let at = ( xorshift ( & mut seed) as usize ) % data.len ( ) ;
1728
data[ at] ^ = 1 < < ( xorshift ( & mut seed) % 8 ) ;
1729
}
1730
1 => {
1731
let at = ( xorshift ( & mut seed) as usize ) % data.len ( ) ;
1732
data[ at] = ( xorshift ( & mut seed) & 0xff ) as u8 ;
1733
}
1734
2 => {
1735
let keep = ( xorshift ( & mut seed) as usize ) % data.len ( ) ;
1736
data.truncate ( keep) ;
1737
}
1738
_ => {
1739
let at = ( xorshift ( & mut seed) as usize ) % data.len ( ) ;
1740
let n = ( ( xorshift ( & mut seed) % 8 ) + 1 ) as usize ;
1741
for k in 0 .. n.min ( data.len ( ) - at) {
1742
data[ at + k] = 0xff ;
1743
}
1744
}
1745
}
1746
fs:: write( path, & data) .unwrap ( ) ;
1747
1748
1749
if let Ok ( s2) = Store:: open( & objects) {
1750
for r in & refs {
1751
match s2.get ( r) {
75167672 Andy McCall 1 month ago
1752
Ok ( ( ty, body) ) => assert_eq! (
1753
hash_object ( ty, & body) ,
1754
* r,
1755
" a returned object always matches its reference"
1756
) ,
1757
Err ( StoreError:: Corrupt( .. ) )
1758
| Err ( StoreError:: NotFound( _ ) )
1759
| Err ( StoreError:: Io( _ ) ) => { }
2b872d07 Andy McCall 1 month ago ↶
1760
Err ( e) => panic! ( " unexpected error {e}" ) ,
1761
}
1762
}
1763
}
1764
fs:: write( path, pristine) .unwrap ( ) ;
1765
}
1766
1767
let s3 = Store:: open( & objects) .unwrap ( ) ;
1768
for r in & refs {
1769
let ( ty, body) = s3.get ( r) .unwrap ( ) ;
1770
assert_eq! ( hash_object ( ty, & body) , * r) ;
1771
}
1772
}
8f10a07a Andy McCall 1 month ago ↶
1773
1774
1775
1776
# [ test ]
1777
fn batches_write_loose_or_one_pack ( ) {
1778
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1779
let s = Store:: open( dir.path ( ) .join ( " objects" ) ) .unwrap ( ) ;
1780
let outer = s.begin_batch ( ) .unwrap ( ) ;
1781
let a = s.put ( ObjectType:: Chunk, b " small a" ) .unwrap ( ) ;
1782
{
1783
let inner = s.begin_batch ( ) .unwrap ( ) ;
1784
let b = s.put ( ObjectType:: Chunk, b " small b" ) .unwrap ( ) ;
1785
assert! ( s.has ( & b) ) ;
1786
assert_eq! ( s.get ( & b) .unwrap ( ) .1 , b " small b" ) ;
1787
inner.finish ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1788
assert_eq! (
1789
s.loose_count ( ) .unwrap ( ) ,
1790
0 ,
1791
" the inner batch defers to the outer one"
1792
) ;
8f10a07a Andy McCall 1 month ago ↶
1793
}
1794
assert_eq! ( s.get ( & a) .unwrap ( ) .1 , b " small a" ) ;
75167672 Andy McCall 1 month ago
1795
assert_eq! (
1796
s.put ( ObjectType:: Chunk, b " small a" ) .unwrap ( ) ,
1797
a,
1798
" idempotent while pending"
1799
) ;
8f10a07a Andy McCall 1 month ago ↶
1800
outer.finish ( ) .unwrap ( ) ;
1801
assert_eq! ( s.loose_count ( ) .unwrap ( ) , 2 ) ;
1802
assert_eq! ( s.pack_count ( ) , 0 ) ;
1803
assert_eq! ( s.get ( & a) .unwrap ( ) .1 , b " small a" ) ;
1804
1805
1806
let big = s.begin_batch ( ) .unwrap ( ) ;
1807
let mut refs = Vec :: new( ) ;
1808
let mut seed = 7 u64 ;
1809
for i in 0 .. 300 u32 {
1810
let mut body = vec! [ b ' z' ; 40_000 ] ;
1811
if i % 3 = = 0 {
1812
for b in body.iter_mut ( ) {
1813
* b = ( xorshift ( & mut seed) & 0xff ) as u8 ;
1814
}
1815
}
1816
body[ .. 4 ] .copy_from_slice ( & i.to_le_bytes ( ) ) ;
75167672 Andy McCall 1 month ago
1817
let r = if i % 5 = = 0 {
1818
s.put_chunk_nocompress ( & body) .unwrap ( )
1819
} else {
1820
s.put ( ObjectType:: Chunk, & body) .unwrap ( )
1821
} ;
1822
assert_eq! (
1823
s.get ( & r) .unwrap ( ) .1 ,
1824
body,
1825
" readable while pending (object {i})"
1826
) ;
8f10a07a Andy McCall 1 month ago ↶
1827
refs.push ( ( r, body) ) ;
1828
}
1829
assert! ( s.has ( & refs[ 0 ] .0 ) ) ;
1830
big.finish ( ) .unwrap ( ) ;
1831
assert_eq! ( s.pack_count ( ) , 1 ) ;
75167672 Andy McCall 1 month ago
1832
assert_eq! (
1833
s.loose_count ( ) .unwrap ( ) ,
1834
2 ,
1835
" nothing from the big batch is loose"
1836
) ;
1837
assert! ( fs:: read_dir( dir.path ( ) .join ( " objects" ) )
1838
.unwrap ( )
1839
.flatten ( )
1840
.all ( | e | ! e.file_name ( ) .to_string_lossy ( ) .starts_with ( " tmp-" ) ) ) ;
8f10a07a Andy McCall 1 month ago ↶
1841
let s2 = Store:: open( dir.path ( ) .join ( " objects" ) ) .unwrap ( ) ;
1842
for ( r, body) in & refs {
1843
assert_eq! ( s2.get ( r) .unwrap ( ) .1 , * body) ;
1844
}
1845
1846
{
1847
let _b = s2.begin_batch ( ) .unwrap ( ) ;
1848
s2.put ( ObjectType:: Chunk, b " dropped" ) .unwrap ( ) ;
1849
}
75167672 Andy McCall 1 month ago
1850
assert_eq! (
1851
s2.get ( & hash_object ( ObjectType:: Chunk, b " dropped" ) )
1852
.unwrap ( )
1853
.1 ,
1854
b " dropped"
1855
) ;
afaba5f1 Andy McCall 1 month ago ↶
1856
}
1857
1858
# [ test ]
1859
fn get_many_reads_loose_and_packed_objects_in_order ( ) {
1860
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1861
let s = Store:: open( dir.path ( ) .join ( " objects" ) ) .unwrap ( ) ;
1862
let packed: Vec< ObjectRef> = ( 0 .. 20 )
1863
.map ( | i | {
1864
s.put ( ObjectType:: Chunk, format! ( " packed {i} " ) .as_bytes ( ) )
1865
.unwrap ( )
1866
} )
1867
.collect ( ) ;
1868
s.pack_loose ( ) .unwrap ( ) ;
1869
let loose: Vec< ObjectRef> = ( 0 .. 20 )
1870
.map ( | i | {
1871
s.put ( ObjectType:: Chunk, format! ( " loose {i} " ) .as_bytes ( ) )
1872
.unwrap ( )
1873
} )
1874
.collect ( ) ;
1875
let missing = hash_object ( ObjectType:: Chunk, b " never stored" ) ;
1876
let mut refs: Vec< ObjectRef> = Vec :: new( ) ;
1877
for i in 0 .. 20 {
1878
refs.push ( loose[ i] ) ;
1879
refs.push ( packed[ i] ) ;
1880
}
1881
refs.push ( missing) ;
1882
let got = s.get_many ( & refs) ;
1883
assert_eq! ( got.len ( ) , refs.len ( ) ) ;
1884
for ( i, r) in refs.iter ( ) .enumerate ( ) .take ( 40 ) {
1885
let want = if i % 2 = = 0 {
1886
format! ( " loose {} " , i / 2 )
1887
} else {
1888
format! ( " packed {} " , i / 2 )
1889
} ;
1890
let ( ty, body) = got[ i] .as_ref ( ) .unwrap ( ) ;
1891
assert_eq! (
1892
( * ty, body.as_slice ( ) ) ,
1893
( ObjectType:: Chunk, want.as_bytes ( ) ) ,
1894
" {r:?}"
1895
) ;
1896
}
1897
assert! ( matches! ( got[ 40 ] , Err ( StoreError:: NotFound( _ ) ) ) ) ;
a9f686ed Andy McCall 1 month ago ↶
1898
}
1899
}
1900
1901
# [ cfg ( test ) ]
1902
mod batch_tests {
1903
use super :: * ;
1904
1905
# [ test ]
1906
fn a_large_batch_packs_every_object_and_indexes_them_all ( ) {
1907
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1908
let store = Store:: open( dir.path ( ) ) .unwrap ( ) ;
1909
let mut refs = Vec :: new( ) ;
1910
{
1911
let batch = store.begin_batch ( ) .unwrap ( ) ;
1912
for i in 0 .. 1000 u32 {
1913
let body = format! ( " object {i} {} " , " x" .repeat ( 20_000 + i as usize ) ) .into_bytes ( ) ;
1914
refs.push ( store.put ( ObjectType:: Blob, & body) .unwrap ( ) ) ;
1915
}
1916
batch.finish ( ) .unwrap ( ) ;
1917
}
1918
let again = Store:: open( dir.path ( ) ) .unwrap ( ) ;
1919
for r in & refs {
1920
again
1921
.get ( r)
1922
.unwrap_or_else ( | e | panic! ( " {}: {e}" , r.short ( 12 ) ) ) ;
1923
}
1924
}
1925
}
1926
1927
# [ cfg ( test ) ]
1928
mod size_tests {
1929
use super :: * ;
1930
1931
# [ test ]
1932
fn an_object_over_the_limit_is_refused_and_nothing_is_written ( ) {
1933
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1934
let store = Store:: open( dir.path ( ) ) .unwrap ( ) ;
1935
let big = vec! [ b ' x' ; MAX_OBJECT_LEN as usize + 1 ] ;
1936
{
1937
let batch = store.begin_batch ( ) .unwrap ( ) ;
1938
store.put ( ObjectType:: Blob, b " small" ) .unwrap ( ) ;
1939
let e = store.put ( ObjectType:: Change, & big) .unwrap_err ( ) ;
1940
assert! (
1941
matches! ( e, StoreError:: TooLarge { kind: " change" , .. } ) ,
1942
" {e}"
1943
) ;
1944
assert! ( e.to_string ( ) .contains ( " commit it in parts" ) , " {e}" ) ;
1945
batch.finish ( ) .unwrap ( ) ;
1946
}
1947
1948
let again = Store:: open( dir.path ( ) ) .unwrap ( ) ;
1949
again.get ( & hash_object ( ObjectType:: Blob, b " small" ) ) .unwrap ( ) ;
6f8aeb99 Andy McCall 1 month ago ↶
1950
}
1951
}
1952
1953
# [ cfg ( test ) ]
1954
mod text_tests {
1955
use super :: * ;
1956
use crate :: objects:: { AtomRef, Edit, CHANGE_VERSION } ;
1957
1958
fn large_change ( ) -> Change {
1959
let mut c = crate :: objects:: tests:: sample_change( ) ;
1960
assert_eq! ( c.version, CHANGE_VERSION ) ;
1961
c.edits[ 0 ] = Edit:: Insert {
1962
node: crate :: ids:: NodeId( [ 7 ; 16 ] ) ,
1963
after: AtomRef:: Start,
1964
before: AtomRef:: End,
1965
lines: ( 0 .. 2000 )
1966
.map ( | i | format! ( " line {i} \n " ) .into_bytes ( ) )
1967
.collect ( ) ,
1968
} ;
1969
c
1970
}
1971
1972
# [ test ]
1973
fn a_large_insert_is_stored_beside_its_change_and_read_back ( ) {
1974
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1975
let s = Store:: open( dir.path ( ) ) .unwrap ( ) ;
1976
let c = large_change ( ) ;
1977
let r = s.put_change ( & c) .unwrap ( ) ;
1978
assert_eq! ( r, c.hash ( ) ) ;
1979
let ( _ , body) = s.get ( & r) .unwrap ( ) ;
1980
assert! (
1981
body.len ( ) < 1024 ,
1982
" the change holds a reference, not {} bytes of lines" ,
1983
body.len ( )
1984
) ;
1985
let ( back, parts) = s.get_change_parts ( & r) .unwrap ( ) ;
1986
assert_eq! ( back, c) ;
1987
1988
assert_eq! ( parts.len ( ) , 2 ) ;
1989
for p in & parts {
1990
assert! ( s.has ( p) ) ;
1991
}
1992
1993
let n = s.list ( ) .unwrap ( ) .len ( ) ;
1994
s.put_change ( & c) .unwrap ( ) ;
1995
assert_eq! ( s.list ( ) .unwrap ( ) .len ( ) , n) ;
1996
}
1997
1998
# [ test ]
1999
fn a_change_whose_text_blob_is_missing_is_reported_not_misread ( ) {
2000
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2001
let s = Store:: open( dir.path ( ) ) .unwrap ( ) ;
2002
let c = large_change ( ) ;
2003
let r = s.put_change ( & c) .unwrap ( ) ;
2004
let ( _ , parts) = s.get_change_parts ( & r) .unwrap ( ) ;
2005
fs:: remove_file( s.path_for ( & parts[ 0 ] ) ) .unwrap ( ) ;
2006
let e = s.get_change ( & r) .unwrap_err ( ) ;
2007
assert! (
2008
matches! ( & e, StoreError:: Corrupt( h, why) if * h = = r & & why.contains ( " missing" ) ) ,
2009
" {e}"
2010
) ;
192fad1e Andy McCall 1 month ago ↶
2011
}
2012
}
2013
2014
# [ cfg ( test ) ]
2015
mod put_many_tests {
2016
use super :: * ;
2017
2018
# [ test ]
2019
fn many_objects_in_a_batch_are_compressed_together_and_all_read_back ( ) {
2020
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
2021
let s = Store:: open( dir.path ( ) ) .unwrap ( ) ;
2022
let before = s.put ( ObjectType:: Chunk, b " already here" ) .unwrap ( ) ;
2023
let bodies: Vec< Vec< u8 > > = ( 0 .. 200 u32 )
2024
.map ( | i | format! ( " chunk {i} " ) .repeat ( 6000 ) .into_bytes ( ) )
2025
.collect ( ) ;
2026
let mut objects: Vec< ( ObjectType, & [u8 ]) > = bodies
2027
.iter ( )
2028
.map ( | b | ( ObjectType:: Chunk, b.as_slice ( ) ) )
2029
.collect ( ) ;
2030
objects.push ( ( ObjectType:: Chunk, bodies[ 0 ] .as_slice ( ) ) ) ;
2031
objects.push ( ( ObjectType:: Chunk, b " already here" ) ) ;
2032
let refs = {
2033
let batch = s.begin_batch ( ) .unwrap ( ) ;
2034
let refs = s.put_many ( & objects) .unwrap ( ) ;
2035
batch.finish ( ) .unwrap ( ) ;
2036
refs
2037
} ;
2038
assert_eq! ( refs.len ( ) , objects.len ( ) ) ;
2039
assert_eq! ( refs[ 200 ] , refs[ 0 ] , " a repeat is the same object" ) ;
2040
assert_eq! ( refs[ 201 ] , before) ;
2041
let again = Store:: open( dir.path ( ) ) .unwrap ( ) ;
2042
for ( r, b) in refs.iter ( ) .zip ( & objects) {
2043
assert_eq! ( again.get ( r) .unwrap ( ) .1 , b.1 ) ;
2044
}
8f10a07a Andy McCall 1 month ago ↶
2045
}
ff0af1ee unknown 1 month ago
2046
}