1234module cli56import json27import 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(json2.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('-' + 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}125126127128fn 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}