cmd_save.v

2.8 KB · raw

1//! `koh save` and `koh checkpoint`.23module cli45import json26import src.fmt7import src.history8import src.snapshot910pub fn cmd_save(raw []string, is_checkpoint bool) int {11	verb := if is_checkpoint { 'checkpoint' } else { 'save' }12	args := parse(verb, raw, Options{13		values: ['agent', 'session']14		flags:  ['json']15	}) or { return usage_error(err.msg()) }16	message := args.positional_at(0)17	if message == '' {18		example := if is_checkpoint {19			'before combine replay engine'20		} else {21			'fix off-by-one in chunk boundary'22		}23		return usage_error('${verb} needs a message, e.g. koh ${verb} "${example}"')24	}25	store := open_store() or { return refuse(args.json(), err.msg()) }26	defer {27		store.close()28	}29	mut hashing := reading()30	walked := snapshot.walk_report(store, store.root, snapshot.load(store.root), mut hashing) or {31		return refuse(args.json(), err.msg())32	}33	save := history.save_worktree(store, store.root, snapshot.load(store.root), history.SaveInput{34		message:       message35		agent:         args.value('agent')36		session:       args.value('session')37		is_checkpoint: is_checkpoint38		storing:       fn (done int, total int) {39			if done % 100 == 1 || done == total {40				working('storing ${done} / ${total} files')41			}42		}43	}) or { return refuse(args.json(), err.msg()) }4445	files := history.files_of(store, save.id) or { []snapshot.FileEntry{} }46	if args.json() {47		emit_json(json2.encode(SaveOut{48			id:            save.id49			parent:        save.parent50			message:       save.message51			created_at:    save.created_at52			machine:       save.machine53			is_checkpoint: save.is_checkpoint54			agent:         save.agent55			session:       save.session56			files:         files.len57			size_bytes:    save.size_bytes58			unsafe_paths:  walked.unsafe_paths59		}))60		auto_offer(store, true)61		return 062	}63	for path in walked.unsafe_paths {64		warn('not saved: unsafe name ${path}')65	}66	ok('saved [${save.id}]  ${save.message}')67	mut detail := []string{}68	if save.is_checkpoint {69		detail << 'checkpoint'70	}71	detail << fmt.stamp(save.created_at)72	detail << fmt.plural(files.len, 'file')73	detail << fmt.human_bytes(save.size_bytes)74	note(detail.join(' · '))75	mut attribution := []string{}76	if save.agent != '' {77		attribution << 'agent: ${save.agent}'78	}79	if save.session != '' {80		attribution << 'session: ${save.session}'81	}82	if attribution.len > 0 {83		note(attribution.join(' · '))84	}85	auto_offer(store, false)86	return 087}8889// Every field is always present, so a consumer never has to tell "absent" from90// "missing key". An empty string means the save recorded none.91struct SaveOut {92	id            string93	parent        string94	message       string95	created_at    i6496	machine       string97	is_checkpoint bool98	agent         string99	session       string100	files         int101	size_bytes    i64102	unsafe_paths  []string103}