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.fmt8import src.history9import src.snapshot10import src.storage1112pub fn cmd_diff(raw []string) int {13	args := parse('diff', raw, Options{14		values: ['session']15		flags:  ['json']16	}) or { return usage_error(err.msg()) }17	store := open_store() or { return refuse(args.json(), err.msg()) }18	defer {19		store.close()20	}21	sides := resolve_sides(store, args) or { return refuse(args.json(), err.msg()) }22	deltas := snapshot.diff_trees(store, sides.from, sides.to) or {23		return refuse(args.json(), err.msg())24	}25	if args.json() {26		emit_json(json.encode(DiffJson{27			from:  sides.from_name28			to:    sides.to_name29			files: deltas.map(delta_json(it))30		}))31		return 032	}33	if deltas.len == 0 {34		ok('no changes')35		return 036	}37	for delta in deltas {38		print(delta.patch)39		if !delta.patch.ends_with('\n') {40			println('')41		}42	}43	mut added := 044	mut removed := 045	for delta in deltas {46		added += delta.added47		removed += delta.removed48	}49	note('${fmt.plural(deltas.len, 'file')} changed  ·  ${fmt.green('+' + added.str())} ${fmt.red(50		'-' + removed.str())}')51	return 052}5354struct Sides {55pub:56	from      snapshot.Tree57	to        snapshot.Tree58	from_name string59	to_name   string60}6162struct DiffJson {63	from  string64	to    string65	files []DeltaJson66}6768struct DeltaJson {69	path    string70	change  string71	added   int72	removed int73	binary  bool74	patch   string75}7677fn delta_json(delta snapshot.Delta) DeltaJson {78	return DeltaJson{79		path:    delta.path80		change:  delta.kind81		added:   delta.added82		removed: delta.removed83		binary:  delta.binary84		patch:   delta.patch85	}86}8788fn resolve_sides(store &storage.Store, args Args) !Sides {89	if expr := args.value_or_none('session') {90		return session_sides(store, expr)!91	}92	match args.positional.len {93		0 {94			head := history.head(store) or { return error('this repo has no saves yet') }95			return Sides{96				from:      tree_of(store, head.id)!97				to:        working_tree(store)!98				from_name: head.id99				to_name:   'working'100			}101		}102		1 {103			save := history.resolve(store, args.positional_at(0))!104			return Sides{105				from:      tree_of(store, save.id)!106				to:        working_tree(store)!107				from_name: save.id108				to_name:   'working'109			}110		}111		2 {112			first := history.resolve(store, args.positional_at(0))!113			second := history.resolve(store, args.positional_at(1))!114			return Sides{115				from:      tree_of(store, first.id)!116				to:        tree_of(store, second.id)!117				from_name: first.id118				to_name:   second.id119			}120		}121		else {122			return error('diff takes one save ID, or two to compare saves')123		}124	}125}126127// session_sides spans a whole session: from the tree just before its first128// save to its last save.129fn session_sides(store &storage.Store, expr string) !Sides {130	name := if expr == 'last' { history.last_session(store)! } else { expr }131	saves := history.session_saves(store, name)!132	if saves.len == 0 {133		return error('no saves in session "${name}"')134	}135	first := saves[0]136	last := saves[saves.len - 1]137	mut from := snapshot.Tree{}138	mut from_name := 'empty'139	if first.parent != '' {140		from = tree_of(store, first.parent)!141		from_name = first.parent142	}143	return Sides{144		from:      from145		to:        tree_of(store, last.id)!146		from_name: from_name147		to_name:   last.id148	}149}