1234module cli56import json27import os8import src.events9import src.fmt10import src.history11import src.snapshot12import src.storage1314pub fn cmd_status(raw []string) int {15 args := parse('status', raw, Options{16 flags: ['json']17 }) or { return usage_error(err.msg()) }18 store := open_store() or { return refuse(args.json(), err.msg()) }19 defer {20 store.close()21 }22 head := history.head(store) or {23 return refuse(args.json(), 'this repo has no saves yet — run "koh save \\"first save\\""')24 }25 unfinished := snapshot.restore_pending(store)26 changes := compare_with_head(store, head) or {27 28 reason := if unfinished {29 '${err.msg()} — ${snapshot.restore_unfinished}'30 } else {31 err.msg()32 }33 return refuse(args.json(), reason)34 }3536 if args.json() {37 line := history.line(store) or { return refuse(true, err.msg()) }38 flags := events.latest_flags(store) or { return refuse(true, err.msg()) }39 head_flag := flags[head.id] or { events.Flag{} }40 emit_json(json2.encode(StatusJson{41 head_id: head.id42 head_msg: head.message43 dirty: changes.dirty44 modified: changes.modified45 added: changes.added46 deleted: changes.removed47 ignore_changed: changes.ignore_changed48 restore_unfinished: unfinished49 saves: line.len50 size_bytes: os.file_size(os.join_path(store.root, storage.file_name))51 head_flagged: head_flag.flagged52 unsafe_paths: changes.unsafe_paths53 }))54 return 055 }56 saves := history.save_count(store) or { return refuse(false, err.msg()) }57 note('at [${head.id}] ${fmt.fit(head.message.all_before('\n'), 50)} · ${ago(head.created_at)} · ${fmt.plural(int(saves), 'save')}')58 if unfinished {59 warn(snapshot.restore_unfinished)60 }61 for path in changes.unsafe_paths {62 warn('not saved: unsafe name ${path}')63 }64 if !changes.dirty {65 ok('no changes')66 if changes.ignore_changed {67 note('${snapshot.ignore_file} changed since [${head.id}]')68 }69 return 070 }71 for path in changes.added {72 println(' ${fmt.green('+')} ${path}')73 }74 for path in changes.modified {75 println(' ${fmt.amber('~')} ${path}')76 }77 for path in changes.removed {78 println(' ${fmt.red('-')} ${path}')79 }80 note('${changes.added.len} added · ${changes.modified.len} modified · ${changes.removed.len} removed')81 if changes.ignore_changed {82 warn('${snapshot.ignore_file} changed since [${head.id}] — the next save records the new rules')83 }84 return 085}8687pub struct Changes {88pub:89 dirty bool90 added []string91 modified []string92 removed []string93 ignore_changed bool94 unsafe_paths []string95}9697struct StatusJson {98 head_id string99 head_msg string100 dirty bool101 modified []string102 added []string103 deleted []string104 ignore_changed bool105 restore_unfinished bool106 saves int107 size_bytes u64108 head_flagged bool109 unsafe_paths []string110}111112pub fn compare_with_head(store &storage.Store, head history.Save) !Changes {113 before := snapshot.index_files(history.files_of(store, head.id)!)114 walked := snapshot.walk_report(store, store.root, snapshot.load(store.root))!115 after := snapshot.index_files(walked.files)116 mut added := []string{}117 mut modified := []string{}118 mut removed := []string{}119 for path in after.keys() {120 if path !in before {121 added << path122 } else if before[path].hash != after[path].hash123 || before[path].executable != after[path].executable {124 modified << path125 }126 }127 for path in before.keys() {128 if path !in after {129 removed << path130 }131 }132 added.sort()133 modified.sort()134 removed.sort()135 ignore_changed := snapshot.ignore_file in added || snapshot.ignore_file in modified136 return Changes{137 dirty: added.len + modified.len + removed.len > 0138 added: added139 modified: modified140 removed: removed141 ignore_changed: ignore_changed142 unsafe_paths: walked.unsafe_paths143 }144}