Skip to content

Commit 20d25fd

Browse files
committed
Remvoe extra files when restoring snapshots
Signed-off-by: kerthcet <kerthcet@gmail.com>
1 parent b3d8c9a commit 20d25fd

1 file changed

Lines changed: 99 additions & 3 deletions

File tree

sandd/src/snapshot/manager.rs

Lines changed: 99 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ impl SnapshotManager {
200200
Ok(())
201201
}
202202

203-
/// Restore tree recursively
203+
/// Restore tree recursively (always clean - deletes extras after successful restore)
204204
fn restore_tree<'a>(
205205
&'a self,
206206
tree_hash: &'a str,
@@ -209,11 +209,19 @@ impl SnapshotManager {
209209
Box::pin(async move {
210210
fs::create_dir_all(dest).await?;
211211

212-
// Load tree object
212+
// Load tree object - tells us what SHOULD exist
213213
let tree_json = self.store.get_blob(tree_hash).await?;
214214
let tree: Tree = serde_json::from_slice(&tree_json)?;
215215

216-
// Restore each entry
216+
// Build set of expected names in this directory (owned strings to avoid borrow issues)
217+
let expected_names: std::collections::HashSet<String> = tree
218+
.entries
219+
.iter()
220+
.map(|e| e.name.clone())
221+
.collect();
222+
223+
// Phase 1: Restore each entry from snapshot
224+
// Do this FIRST - if restore fails, extras remain untouched (safer)
217225
for entry in tree.entries {
218226
let entry_path = dest.join(&entry.name);
219227

@@ -281,6 +289,29 @@ impl SnapshotManager {
281289
}
282290
}
283291

292+
// Phase 2: Clean this directory - delete extras (only after successful restore)
293+
// Cleanup failures are warned but don't fail the operation
294+
let mut read_dir = fs::read_dir(dest).await?;
295+
while let Some(entry) = read_dir.next_entry().await? {
296+
let name = entry.file_name();
297+
let name_str = name.to_string_lossy().to_string();
298+
299+
if !expected_names.contains(&name_str) {
300+
let path = entry.path();
301+
302+
// Not in snapshot - delete it
303+
if path.is_dir() {
304+
if let Err(e) = fs::remove_dir_all(&path).await {
305+
tracing::warn!("Failed to delete directory {}: {}", path.display(), e);
306+
}
307+
} else {
308+
if let Err(e) = fs::remove_file(&path).await {
309+
tracing::warn!("Failed to delete file {}: {}", path.display(), e);
310+
}
311+
}
312+
}
313+
}
314+
284315
Ok(())
285316
})
286317
}
@@ -1189,4 +1220,69 @@ mod tests {
11891220
assert!(result.is_err());
11901221
assert!(result.unwrap_err().to_string().contains("does not exist"));
11911222
}
1223+
1224+
#[tokio::test]
1225+
async fn test_restore_always_clean() {
1226+
let temp_dir = TempDir::new().unwrap();
1227+
let store_dir = temp_dir.path().join("store");
1228+
let workspace = temp_dir.path().join("workspace");
1229+
let restore_dir = temp_dir.path().join("restored");
1230+
1231+
// Create snapshot with specific files
1232+
fs::create_dir_all(&workspace).await.unwrap();
1233+
fs::write(workspace.join("file1.txt"), "content1")
1234+
.await
1235+
.unwrap();
1236+
fs::create_dir_all(workspace.join("dir1")).await.unwrap();
1237+
fs::write(workspace.join("dir1/file2.txt"), "content2")
1238+
.await
1239+
.unwrap();
1240+
1241+
let manager = SnapshotManager::new(store_dir).unwrap();
1242+
let snapshot_id = manager
1243+
.create_snapshot(&workspace, Some("Clean test".to_string()), None)
1244+
.await
1245+
.unwrap();
1246+
1247+
// Restore to directory with extra files
1248+
fs::create_dir_all(&restore_dir).await.unwrap();
1249+
fs::write(restore_dir.join("extra_file.txt"), "should be deleted")
1250+
.await
1251+
.unwrap();
1252+
fs::create_dir_all(restore_dir.join("extra_dir"))
1253+
.await
1254+
.unwrap();
1255+
fs::write(restore_dir.join("extra_dir/nested.txt"), "also deleted")
1256+
.await
1257+
.unwrap();
1258+
fs::create_dir_all(restore_dir.join("dir1")).await.unwrap();
1259+
fs::write(restore_dir.join("dir1/extra_in_dir.txt"), "delete me")
1260+
.await
1261+
.unwrap();
1262+
1263+
// Restore snapshot (should clean extras)
1264+
manager
1265+
.restore_snapshot(&snapshot_id, &restore_dir)
1266+
.await
1267+
.unwrap();
1268+
1269+
// Verify exact match - only snapshot files exist
1270+
assert!(restore_dir.join("file1.txt").exists());
1271+
assert!(restore_dir.join("dir1/file2.txt").exists());
1272+
1273+
// Verify extras are deleted
1274+
assert!(!restore_dir.join("extra_file.txt").exists());
1275+
assert!(!restore_dir.join("extra_dir").exists());
1276+
assert!(!restore_dir.join("dir1/extra_in_dir.txt").exists());
1277+
1278+
// Verify content is correct
1279+
let content1 = fs::read_to_string(restore_dir.join("file1.txt"))
1280+
.await
1281+
.unwrap();
1282+
assert_eq!(content1, "content1");
1283+
let content2 = fs::read_to_string(restore_dir.join("dir1/file2.txt"))
1284+
.await
1285+
.unwrap();
1286+
assert_eq!(content2, "content2");
1287+
}
11921288
}

0 commit comments

Comments
 (0)