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 print_status(head, changes, unfinished, int(saves))58 return 059}60616263fn print_status(head history.Save, changes Changes, unfinished bool, saves int) {64 note('at [${head.id}] ${fmt.fit(head.message.all_before('\n'), 50)} · ${ago(head.created_at)} · ${fmt.plural(saves, 'save')}')65 if unfinished {66 warn(snapshot.restore_unfinished)67 }68 for path in changes.unsafe_paths {69 warn('not saved: unsafe name ${path}')70 }71 if !changes.dirty {72 ok('no changes')73 if changes.ignore_changed {74 note('${snapshot.ignore_file} changed since [${head.id}]')75 }76 return77 }78 for path in changes.added {79 println(' ${fmt.green('+')} ${path}')80 }81 for path in changes.modified {82 println(' ${fmt.amber('~')} ${path}')83 }84 for path in changes.removed {85 println(' ${fmt.red('-')} ${path}')86 }87 note('${changes.added.len} added · ${changes.modified.len} modified · ${changes.removed.len} removed')88 if changes.ignore_changed {89 warn('${snapshot.ignore_file} changed since [${head.id}] — the next save records the new rules')90 }91}9293pub struct Changes {94pub:95 dirty bool96 added []string97 modified []string98 removed []string99 ignore_changed bool100 unsafe_paths []string101}102103struct StatusJson {104 head_id string105 head_msg string106 dirty bool107 modified []string108 added []string109 deleted []string110 ignore_changed bool111 restore_unfinished bool112 saves int113 size_bytes u64114 head_flagged bool115 unsafe_paths []string116}117118pub fn compare_with_head(store &storage.Store, head history.Save) !Changes {119 before := snapshot.index_files(history.files_of(store, head.id)!)120 mut hashing := reading()121 walked := snapshot.walk_report(store, store.root, snapshot.load(store.root), mut hashing)!122 after := snapshot.index_files(walked.files)123 mut added := []string{}124 mut modified := []string{}125 mut removed := []string{}126 for path in after.keys() {127 if path !in before {128 added << path129 } else if before[path].hash != after[path].hash130 || before[path].executable != after[path].executable {131 modified << path132 }133 }134 for path in before.keys() {135 if path !in after {136 removed << path137 }138 }139 added.sort()140 modified.sort()141 removed.sort()142 ignore_changed := snapshot.ignore_file in added || snapshot.ignore_file in modified143 return Changes{144 dirty: added.len + modified.len + removed.len > 0145 added: added146 modified: modified147 removed: removed148 ignore_changed: ignore_changed149 unsafe_paths: walked.unsafe_paths150 }151}