fix: preserve archive restores after pruning
This commit is contained in:
+10
-10
@@ -7,7 +7,7 @@
|
||||
"typings": "dist_ts/index.d.ts",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "(tstest test/ --verbose --timeout 60)",
|
||||
"test": "(pnpm run build && tstest test/ --verbose --timeout 60)",
|
||||
"build": "(tsrust && tsbuild tsfolders --allowimplicitany)"
|
||||
},
|
||||
"repository": {
|
||||
@@ -21,17 +21,17 @@
|
||||
},
|
||||
"homepage": "https://code.foss.global/serve.zone/containerarchive",
|
||||
"dependencies": {
|
||||
"@push.rocks/lik": "^6.0.0",
|
||||
"@push.rocks/smartpromise": "^4.0.0",
|
||||
"@push.rocks/smartrust": "^1.3.2",
|
||||
"@push.rocks/smartrx": "^3.0.0"
|
||||
"@push.rocks/lik": "^6.4.1",
|
||||
"@push.rocks/smartpromise": "^4.2.4",
|
||||
"@push.rocks/smartrust": "^1.4.0",
|
||||
"@push.rocks/smartrx": "^3.0.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@git.zone/tsbuild": "^2.0.0",
|
||||
"@git.zone/tsrun": "^1.0.0",
|
||||
"@git.zone/tstest": "^1.0.0",
|
||||
"@git.zone/tsrust": "^1.3.0",
|
||||
"@types/node": "^22.0.0"
|
||||
"@git.zone/tsbuild": "^4.4.0",
|
||||
"@git.zone/tsrun": "^2.0.3",
|
||||
"@git.zone/tsrust": "^1.3.3",
|
||||
"@git.zone/tstest": "^3.6.3",
|
||||
"@types/node": "^25.6.1"
|
||||
},
|
||||
"files": [
|
||||
"ts/**/*",
|
||||
|
||||
Generated
+1903
-4086
File diff suppressed because it is too large
Load Diff
+16
-4
@@ -4,7 +4,7 @@
|
||||
/// deletes expired snapshots, and removes pack files where ALL chunks
|
||||
/// are unreferenced (whole-pack GC only).
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::path::Path;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -233,6 +233,7 @@ async fn rewrite_partial_packs(
|
||||
|
||||
// Read referenced chunks and write them to a new pack
|
||||
let mut new_pack_writer = PackWriter::new(repo.config.pack_target_size);
|
||||
let mut rewritten_offsets: HashMap<String, u64> = HashMap::new();
|
||||
|
||||
for entry in &entries {
|
||||
let hash_hex = hasher::hash_to_hex(&entry.content_hash);
|
||||
@@ -245,6 +246,10 @@ async fn rewrite_partial_packs(
|
||||
&pack_path, entry.offset, entry.compressed_size,
|
||||
).await?;
|
||||
|
||||
let new_offset = new_pack_writer.entries().iter()
|
||||
.map(|existing| existing.compressed_size as u64)
|
||||
.sum::<u64>();
|
||||
|
||||
new_pack_writer.add_chunk(
|
||||
entry.content_hash,
|
||||
&chunk_data,
|
||||
@@ -252,6 +257,7 @@ async fn rewrite_partial_packs(
|
||||
entry.nonce,
|
||||
entry.flags,
|
||||
);
|
||||
rewritten_offsets.insert(hash_hex, new_offset);
|
||||
}
|
||||
|
||||
// Finalize the new pack
|
||||
@@ -267,9 +273,15 @@ async fn rewrite_partial_packs(
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let rewritten_offset = *rewritten_offsets.get(&hash_hex).ok_or_else(|| {
|
||||
ArchiveError::Corruption(format!(
|
||||
"Missing rewritten offset for chunk {} in pack {}",
|
||||
hash_hex, pack_id,
|
||||
))
|
||||
})?;
|
||||
repo.index.add_entry(hash_hex, IndexEntry {
|
||||
pack_id: new_pack_info.pack_id.clone(),
|
||||
offset: entry.offset, // Note: offset in the new pack may differ
|
||||
offset: rewritten_offset,
|
||||
compressed_size: entry.compressed_size,
|
||||
plaintext_size: entry.plaintext_size,
|
||||
nonce,
|
||||
@@ -280,9 +292,9 @@ async fn rewrite_partial_packs(
|
||||
}
|
||||
|
||||
// Delete old pack + idx
|
||||
let old_size = tokio::fs::metadata(&pack_path).await
|
||||
let _old_size = tokio::fs::metadata(&pack_path).await
|
||||
.map(|m| m.len()).unwrap_or(0);
|
||||
let old_idx_size = tokio::fs::metadata(&idx_path).await
|
||||
let _old_idx_size = tokio::fs::metadata(&idx_path).await
|
||||
.map(|m| m.len()).unwrap_or(0);
|
||||
|
||||
let _ = tokio::fs::remove_file(&pack_path).await;
|
||||
|
||||
@@ -185,6 +185,19 @@ tap.test('should prune with keepLast=1', async () => {
|
||||
// Verify only 1 snapshot remains
|
||||
const snapshots = await repo.listSnapshots();
|
||||
expect(snapshots.length).toEqual(1);
|
||||
|
||||
// The remaining snapshot must still restore after partial-pack GC rewrites chunks.
|
||||
const restoreStream = await repo.restore(snapshots[0].id, { item: 'config.tar' });
|
||||
const chunks: Buffer[] = [];
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
restoreStream.on('data', (chunk: Buffer) => chunks.push(chunk));
|
||||
restoreStream.on('end', resolve);
|
||||
restoreStream.on('error', reject);
|
||||
});
|
||||
|
||||
const restored = Buffer.concat(chunks);
|
||||
const expected = Buffer.alloc(32 * 1024, 'item-two-data');
|
||||
expect(restored.equals(expected)).toBeTrue();
|
||||
});
|
||||
|
||||
// ==================== Close ====================
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"types": [
|
||||
"node"
|
||||
],
|
||||
"esModuleInterop": true,
|
||||
"verbatimModuleSyntax": true
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user