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}2627282930pub fn walk_raw(root string, ig Ignore) ![]FileEntry {31 mut entries := []FileEntry{}32 walk_dir(none, root, '', ig, map[string]storage.CacheRow{}, mut entries)!33 entries.sort(a.path < b.path)34 return entries35}3637fn walk_dir(store ?&storage.Store, root string, rel string, ig Ignore, cache map[string]storage.CacheRow, mut out []FileEntry) ! {38 dir := if rel == '' { root } else { os.join_path(root, rel) }39 for name in os.ls(dir)! {40 child := if rel == '' { name } else { '${rel}/${name}' }41 st := lstat(os.join_path(root, child)) or { continue }42 43 44 if ig.matches(child, st.kind == .dir) {45 continue46 }47 match st.kind {48 .dir {49 walk_dir(store, root, child, ig, cache, mut out)!50 }51 .file {52 if st.size > storage.max_file_size {53 return error('${child} is ${st.size} bytes; koh stores code and text, not files this large')54 }55 hash := hash_file(store, root, child, st, cache)!56 out << FileEntry{57 path: child58 hash: hash59 size: st.size60 executable: is_executable(st.mode)61 }62 }63 else {}64 }65 }66}6768fn hash_file(store ?&storage.Store, root string, rel string, st Stat, cache map[string]storage.CacheRow) !string {69 if s := store {70 if row := cache[rel] {71 if row.size == st.size && row.mtime_ns == st.mtime_ns && row.ctime_ns == st.ctime_ns {72 return row.hash73 }74 }75 data := os.read_bytes(os.join_path(root, rel))!76 hash := storage.hash_hex(data)77 s.save_cache(storage.CacheRow{78 path: rel79 size: st.size80 mtime_ns: st.mtime_ns81 ctime_ns: st.ctime_ns82 hash: hash83 })!84 return hash85 }86 return storage.hash_hex(os.read_bytes(os.join_path(root, rel))!)87}88899091pub fn store_objects(store &storage.Store, root string, entries []FileEntry) ! {92 for entry in entries {93 if store.has_object(entry.hash)! {94 continue95 }96 data := os.read_bytes(os.join_path(root, entry.path))!97 stored := store.put_object(data)!98 if stored != entry.hash {99 return error('${entry.path} changed while saving')100 }101 }102}103104105pub fn same_tree(a []FileEntry, b []FileEntry) bool {106 if a.len != b.len {107 return false108 }109 for i in 0 .. a.len {110 if a[i].path != b[i].path || a[i].hash != b[i].hash || a[i].executable != b[i].executable {111 return false112 }113 }114 return true115}