1234module 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}126127128129fn 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}