cmd_status.v

4.3 KB · raw

1//! `koh status` — where things stand: the save you are on, and what the working tree has that it2//! does not.34module 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		// Whatever stopped the restore may also stop this walk.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}6061// print_status is the plain-text report: the save you are on, then each changed path with a62// mark, then the counts.63fn 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}