123module snapshot45import os6import src.storage789pub struct FileEntry {10pub:11 path string12 hash string13 size i6414 executable bool15}16171819pub fn walk(store &storage.Store, root string, ig Ignore) ![]FileEntry {20 cache := store.load_cache()!21 mut entries := []FileEntry{}22 walk_dir(store, root, '', ig, cache, mut entries)!23 entries.sort(a.path < b.path)24 return entries25}2627fn walk_dir(store &storage.Store, root string, rel string, ig Ignore, cache map[string]storage.CacheRow, mut out []FileEntry) ! {28 dir := if rel == '' { root } else { os.join_path(root, rel) }29 for name in os.ls(dir)! {30 child := if rel == '' { name } else { '${rel}/${name}' }31 st := lstat(os.join_path(root, child)) or { continue }32 33 34 if ig.matches(child, st.kind == .dir) {35 continue36 }37 match st.kind {38 .dir {39 walk_dir(store, root, child, ig, cache, mut out)!40 }41 .file {42 if st.size > storage.max_file_size {43 return error('${child} is ${st.size} bytes; koh stores code and text, not files this large')44 }45 hash := hash_file(store, root, child, st, cache)!46 out << FileEntry{47 path: child48 hash: hash49 size: st.size50 executable: is_executable(st.mode)51 }52 }53 else {}54 }55 }56}5758fn hash_file(store &storage.Store, root string, rel string, st Stat, cache map[string]storage.CacheRow) !string {59 if row := cache[rel] {60 if row.size == st.size && row.mtime_ns == st.mtime_ns && row.ctime_ns == st.ctime_ns {61 return row.hash62 }63 }64 data := os.read_bytes(os.join_path(root, rel))!65 hash := storage.hash_hex(data)66 store.save_cache(storage.CacheRow{67 path: rel68 size: st.size69 mtime_ns: st.mtime_ns70 ctime_ns: st.ctime_ns71 hash: hash72 })!73 return hash74}75767778pub fn store_objects(store &storage.Store, root string, entries []FileEntry) ! {79 for entry in entries {80 if store.has_object(entry.hash)! {81 continue82 }83 data := os.read_bytes(os.join_path(root, entry.path))!84 stored := store.put_object(data)!85 if stored != entry.hash {86 return error('${entry.path} changed while saving')87 }88 }89}909192pub fn same_tree(a []FileEntry, b []FileEntry) bool {93 if a.len != b.len {94 return false95 }96 for i in 0 .. a.len {97 if a[i].path != b[i].path || a[i].hash != b[i].hash || a[i].executable != b[i].executable {98 return false99 }100 }101 return true102}