123module snapshot45import os6import time7import src.storage8910const racy_window_ns = i64(3_000_000_000)111213pub struct FileEntry {14pub:15 path string16 hash string17 size i6418 executable bool19}20212223pub fn walk(store &storage.Store, root string, ig Ignore) ![]FileEntry {24 cache := store.load_cache()!25 mut entries := []FileEntry{}26 walk_dir(store, root, '', ig, cache, mut entries)!27 entries.sort(a.path < b.path)28 return entries29}3031323334pub fn walk_raw(root string, ig Ignore) ![]FileEntry {35 mut entries := []FileEntry{}36 walk_dir(none, root, '', ig, map[string]storage.CacheRow{}, mut entries)!37 entries.sort(a.path < b.path)38 return entries39}4041fn walk_dir(store ?&storage.Store, root string, rel string, ig Ignore, cache map[string]storage.CacheRow, mut out []FileEntry) ! {42 dir := if rel == '' { root } else { os.join_path(root, rel) }43 for name in os.ls(dir)! {44 child := if rel == '' { name } else { '${rel}/${name}' }45 st := lstat(os.join_path(root, child)) or { continue }46 47 48 if ig.matches(child, st.kind == .dir) {49 continue50 }51 match st.kind {52 .dir {53 walk_dir(store, root, child, ig, cache, mut out)!54 }55 .file {56 if st.size > storage.max_file_size {57 return error('${child} is ${st.size} bytes; koh stores code and text, not files this large')58 }59 hash := hash_file(store, root, child, st, cache)!60 out << FileEntry{61 path: child62 hash: hash63 size: st.size64 executable: is_executable(st.mode)65 }66 }67 else {}68 }69 }70}7172fn hash_file(store ?&storage.Store, root string, rel string, st Stat, cache map[string]storage.CacheRow) !string {73 if s := store {74 if row := cache[rel] {75 if row.size == st.size && row.mtime_ns == st.mtime_ns && row.ctime_ns == st.ctime_ns {76 return row.hash77 }78 }79 data := os.read_bytes(os.join_path(root, rel))!80 hash := storage.hash_hex(data)81 if !cacheable(st, time.now().unix_nano()) {82 return hash83 }84 s.save_cache(storage.CacheRow{85 path: rel86 size: st.size87 mtime_ns: st.mtime_ns88 ctime_ns: st.ctime_ns89 hash: hash90 })!91 return hash92 }93 return storage.hash_hex(os.read_bytes(os.join_path(root, rel))!)94}9596979899100101fn cacheable(st Stat, now_ns i64) bool {102 changed := if st.ctime_ns > st.mtime_ns { st.ctime_ns } else { st.mtime_ns }103 return now_ns - changed >= racy_window_ns104}105106107108pub fn store_objects(store &storage.Store, root string, entries []FileEntry) ! {109 for entry in entries {110 if store.has_object(entry.hash)! {111 continue112 }113 data := os.read_bytes(os.join_path(root, entry.path))!114 stored := store.put_object(data)!115 if stored != entry.hash {116 return error('${entry.path} changed while saving')117 }118 }119}120121122pub fn same_tree(a []FileEntry, b []FileEntry) bool {123 if a.len != b.len {124 return false125 }126 for i in 0 .. a.len {127 if a[i].path != b[i].path || a[i].hash != b[i].hash || a[i].executable != b[i].executable {128 return false129 }130 }131 return true132}