123module cli45import json26import os7import src.events8import src.fmt9import src.history10import src.snapshot11import src.storage1213pub fn cmd_status(raw []string) int {14 args := parse('status', raw, Options{15 flags: ['json']16 }) or { return usage_error(err.msg()) }17 store := open_store() or { return refuse(args.json(), err.msg()) }18 defer {19 store.close()20 }21 head := history.head(store) or {22 return refuse(args.json(), 'this repo has no saves yet — run "koh save \\"first save\\""')23 }24 unfinished := snapshot.restore_pending(store)25 changes := compare_with_head(store, head) or {26 27 reason := if unfinished {28 '${err.msg()} — ${snapshot.restore_unfinished}'29 } else {30 err.msg()31 }32 return refuse(args.json(), reason)33 }3435 if args.json() {36 line := history.line(store) or { return refuse(true, err.msg()) }37 flags := events.latest_flags(store) or { return refuse(true, err.msg()) }38 head_flag := flags[head.id] or { events.Flag{} }39 emit_json(json2.encode(StatusJson{40 head_id: head.id41 head_msg: head.message42 dirty: changes.dirty43 modified: changes.modified44 added: changes.added45 deleted: changes.removed46 ignore_changed: changes.ignore_changed47 restore_unfinished: unfinished48 saves: line.len49 size_bytes: os.file_size(os.join_path(store.root, storage.file_name))50 head_flagged: head_flag.flagged51 unsafe_paths: changes.unsafe_paths52 }))53 return 054 }55 if unfinished {56 warn(snapshot.restore_unfinished)57 }58 for path in changes.unsafe_paths {59 warn('not saved: unsafe name ${path}')60 }61 if !changes.dirty {62 ok('no changes')63 if changes.ignore_changed {64 note('${snapshot.ignore_file} changed since [${head.id}]')65 }66 return 067 }68 for path in changes.added {69 println(' ${fmt.green('+')} ${path}')70 }71 for path in changes.modified {72 println(' ${fmt.amber('~')} ${path}')73 }74 for path in changes.removed {75 println(' ${fmt.red('-')} ${path}')76 }77 note('${changes.added.len} added · ${changes.modified.len} modified · ${changes.removed.len} removed')78 if changes.ignore_changed {79 warn('${snapshot.ignore_file} changed since [${head.id}] — the next save records the new rules')80 }81 return 082}8384pub struct Changes {85pub:86 dirty bool87 added []string88 modified []string89 removed []string90 ignore_changed bool91 unsafe_paths []string92}9394struct StatusJson {95 head_id string96 head_msg string97 dirty bool98 modified []string99 added []string100 deleted []string101 ignore_changed bool102 restore_unfinished bool103 saves int104 size_bytes u64105 head_flagged bool106 unsafe_paths []string107}108109pub fn compare_with_head(store &storage.Store, head history.Save) !Changes {110 before := snapshot.index_files(history.files_of(store, head.id)!)111 walked := snapshot.walk_report(store, store.root, snapshot.load(store.root))!112 after := snapshot.index_files(walked.files)113 mut added := []string{}114 mut modified := []string{}115 mut removed := []string{}116 for path in after.keys() {117 if path !in before {118 added << path119 } else if before[path].hash != after[path].hash120 || before[path].executable != after[path].executable {121 modified << path122 }123 }124 for path in before.keys() {125 if path !in after {126 removed << path127 }128 }129 added.sort()130 modified.sort()131 removed.sort()132 ignore_changed := snapshot.ignore_file in added || snapshot.ignore_file in modified133 return Changes{134 dirty: added.len + modified.len + removed.len > 0135 added: added136 modified: modified137 removed: removed138 ignore_changed: ignore_changed139 unsafe_paths: walked.unsafe_paths140 }141}