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