//! `koh status` — what the working tree has that head does not. module cli import json import src.fmt import src.history import src.snapshot import src.storage pub fn cmd_status(raw []string) int { args := parse('status', raw, Options{ flags: ['json'] }) or { return usage_error(err.msg()) } store := open_store() or { return refuse(args.json(), err.msg()) } defer { store.close() } head := history.head(store) or { return refuse(args.json(), 'this repo has no saves yet — run "koh save \\"first save\\""') } unfinished := snapshot.restore_pending(store) changes := compare_with_head(store, head) or { // Whatever stopped the restore may also stop this walk. reason := if unfinished { '${err.msg()} — ${snapshot.restore_unfinished}' } else { err.msg() } return refuse(args.json(), reason) } if args.json() { emit_json(json.encode(StatusJson{ head_id: head.id head_msg: head.message dirty: changes.dirty modified: changes.modified added: changes.added deleted: changes.removed ignore_changed: changes.ignore_changed restore_unfinished: unfinished })) return 0 } if unfinished { warn(snapshot.restore_unfinished) } if !changes.dirty { ok('no changes') if changes.ignore_changed { note('${snapshot.ignore_file} changed since [${head.id}]') } return 0 } for path in changes.added { println(' ${fmt.green('+')} ${path}') } for path in changes.modified { println(' ${fmt.amber('~')} ${path}') } for path in changes.removed { println(' ${fmt.red('-')} ${path}') } note('${changes.added.len} added · ${changes.modified.len} modified · ${changes.removed.len} removed') if changes.ignore_changed { warn('${snapshot.ignore_file} changed since [${head.id}] — the next save records the new rules') } return 0 } pub struct Changes { pub: dirty bool added []string modified []string removed []string ignore_changed bool } struct StatusJson { head_id string head_msg string dirty bool modified []string added []string deleted []string ignore_changed bool restore_unfinished bool } pub fn compare_with_head(store &storage.Store, head history.Save) !Changes { before := snapshot.index_files(history.files_of(store, head.id)!) after := snapshot.index_files(snapshot.walk(store, store.root, snapshot.load(store.root))!) mut added := []string{} mut modified := []string{} mut removed := []string{} for path in after.keys() { if path !in before { added << path } else if before[path].hash != after[path].hash || before[path].executable != after[path].executable { modified << path } } for path in before.keys() { if path !in after { removed << path } } added.sort() modified.sort() removed.sort() ignore_changed := snapshot.ignore_file in added || snapshot.ignore_file in modified return Changes{ dirty: added.len + modified.len + removed.len > 0 added: added modified: modified removed: removed ignore_changed: ignore_changed } }