//! Writing a tree back onto disk. //! //! A restore writes exactly the target's files and deletes the tracked files //! the target does not have. Ignored paths are never touched, and every file //! lands through a temp file and a rename, so an interrupted restore leaves a //! readable tree rather than half-written files. module snapshot import os import src.storage pub struct RestoreReport { pub: written int removed int } // restore_files writes exactly `target` into the working tree and deletes the // paths in `remove` (tracked files the target does not have). Ignored paths are // never touched — not written, not deleted — even if a caller asks. Every file // lands through a temp file and a rename, so an interrupted restore leaves a // readable tree rather than half-written files. pub fn restore_files(store &storage.Store, root string, ig Ignore, target []FileEntry, remove []string) !RestoreReport { mut wanted := map[string]bool{} for entry in target { wanted[entry.path] = true } mut removed := 0 for path in remove { full := os.join_path(root, path) if !os.is_file(full) || ig.matches(path, false) || path in wanted { continue } os.rm(full)! removed++ prune_empty_dirs(root, os.dir(full)) } for entry in target { data := store.get_object(entry.hash)! write_file(os.join_path(root, entry.path), data, entry.executable)! } return RestoreReport{ written: target.len removed: removed } } fn write_file(path string, data []u8, executable bool) ! { os.mkdir_all(os.dir(path), os.MkdirParams{})! tmp := '${path}.koh-tmp-${os.getpid()}' os.write_file_array(tmp, data)! mode := if executable { 0o755 } else { 0o644 } os.chmod(tmp, mode)! os.mv(tmp, path, os.MvParams{})! } // prune_empty_dirs clears directories a restore emptied, never the repo root. fn prune_empty_dirs(root string, dir string) { mut current := dir for current.len > root.len && current.starts_with(root) { if (os.ls(current) or { return }).len > 0 { return } os.rmdir(current) or { return } current = os.dir(current) } }