cmd_diff.v

3.3 KB · raw

1//! `koh diff` — unified diff of the working tree against a save, of one save2//! against another, or across a session.34module cli56import json7import src.history8import src.snapshot9import src.storage1011pub fn cmd_diff(raw []string) int {12	args := parse('diff', raw, Options{13		values: ['session']14		flags:  ['json']15	}) or { return usage_error(err.msg()) }16	store := open_store() or { return refuse(args.json(), err.msg()) }17	defer {18		store.close()19	}20	sides := resolve_sides(store, args) or { return refuse(args.json(), err.msg()) }21	deltas := snapshot.diff_trees(store, sides.from, sides.to) or {22		return refuse(args.json(), err.msg())23	}24	if args.json() {25		emit_json(json.encode(DiffJson{26			from:  sides.from_name27			to:    sides.to_name28			files: deltas.map(delta_json(it))29		}))30		return 031	}32	if deltas.len == 0 {33		ok('no changes')34		return 035	}36	for delta in deltas {37		print(delta.patch)38		if !delta.patch.ends_with('\n') {39			println('')40		}41	}42	mut added := 043	mut removed := 044	for delta in deltas {45		added += delta.added46		removed += delta.removed47	}48	note('${plural(deltas.len, 'file')} changed  ·  ${green('+' + added.str())} ${red('-' +49		removed.str())}')50	return 051}5253struct Sides {54pub:55	from      snapshot.Tree56	to        snapshot.Tree57	from_name string58	to_name   string59}6061struct DiffJson {62	from  string63	to    string64	files []DeltaJson65}6667struct DeltaJson {68	path    string69	change  string70	added   int71	removed int72	binary  bool73	patch   string74}7576fn delta_json(delta snapshot.Delta) DeltaJson {77	return DeltaJson{78		path:    delta.path79		change:  delta.kind80		added:   delta.added81		removed: delta.removed82		binary:  delta.binary83		patch:   delta.patch84	}85}8687fn resolve_sides(store &storage.Store, args Args) !Sides {88	if expr := args.value_or_none('session') {89		return session_sides(store, expr)!90	}91	match args.positional.len {92		0 {93			head := history.head(store) or { return error('this repo has no saves yet') }94			return Sides{95				from:      tree_of(store, head.id)!96				to:        working_tree(store)!97				from_name: head.id98				to_name:   'working'99			}100		}101		1 {102			save := history.resolve(store, args.positional_at(0))!103			return Sides{104				from:      tree_of(store, save.id)!105				to:        working_tree(store)!106				from_name: save.id107				to_name:   'working'108			}109		}110		2 {111			first := history.resolve(store, args.positional_at(0))!112			second := history.resolve(store, args.positional_at(1))!113			return Sides{114				from:      tree_of(store, first.id)!115				to:        tree_of(store, second.id)!116				from_name: first.id117				to_name:   second.id118			}119		}120		else {121			return error('diff takes one save ID, or two to compare saves')122		}123	}124}125126// session_sides spans a whole session: from the tree just before its first127// save to its last save.128fn session_sides(store &storage.Store, expr string) !Sides {129	name := if expr == 'last' { history.last_session(store)! } else { expr }130	saves := history.session_saves(store, name)!131	if saves.len == 0 {132		return error('no saves in session "${name}"')133	}134	first := saves[0]135	last := saves[saves.len - 1]136	mut from := snapshot.Tree{}137	mut from_name := 'empty'138	if first.parent != '' {139		from = tree_of(store, first.parent)!140		from_name = first.parent141	}142	return Sides{143		from:      from144		to:        tree_of(store, last.id)!145		from_name: from_name146		to_name:   last.id147	}148}