12345678module cli910import json211import src.apply12import src.fmt13import src.history14import src.snapshot15import src.storage16171819pub fn cmd_pluck(raw []string) int {20 args := parse('pluck', raw, Options{21 flags: ['reverse', 'dry-run', 'yes', 'json']22 }) or { return usage_error(err.msg()) }23 store := open_store() or { return refuse(args.json(), err.msg()) }24 defer {25 store.close()26 }27 id := args.positional_at(0)28 if id == '' {29 return usage_error('pluck needs a save ID')30 }31 source := history.resolve(store, id) or { return refuse(args.json(), err.msg()) }32 mut filters := []string{}33 if args.positional.len > 1 {34 filters = args.positional[1..]35 }36 37 38 if !args.flag('dry-run') {39 require_clean(store) or { return refuse(args.json(), err.msg()) }40 }41 reverse := args.flag('reverse')42 verb := if reverse { 'pluck reverse' } else { 'pluck' }4344 plan, _ := plan_pluck(store, source, reverse, filters) or {45 return refuse(args.json(), err.msg())46 }4748 if args.flag('dry-run') || plan.applied.len == 0 {49 if args.json() {50 emit_json(json2.encode(PluckJson{51 source: source.id52 reverse: reverse53 applied: plan.applied54 applied_ok: true55 }))56 return 057 }58 if plan.applied.len == 0 {59 ok('nothing to apply from [${source.id}]')60 return 061 }62 ok('${verb} [${source.id}] would change:')63 for path in plan.applied {64 println(' ${fmt.amber('~')} ${path}')65 }66 note('${fmt.plural(plan.applied.len, 'file')} — run again without --dry-run to apply')67 return 068 }6970 ok('${verb}: [${source.id}] ${source.message}')71 for path in plan.applied {72 println(' ${fmt.amber('~')} ${path}')73 }74 note('${fmt.plural(plan.applied.len, 'file')} change')75 approved := confirm(args.flag('yes')) or { return refuse(args.json(), err.msg()) }76 if !approved {77 return 178 }79 80 81 require_clean(store) or { return refuse(args.json(), err.msg()) }82 fresh, current := plan_pluck(store, source, reverse, filters) or {83 return refuse(args.json(), err.msg())84 }85 if fresh.applied.len == 0 {86 return refuse(args.json(),87 'nothing to apply from [${source.id}] any more — the tree changed while waiting')88 }89 90 91 snapshot.mark_restore(store) or { return refuse(args.json(), err.msg()) }92 save := history.save_tree(store, store.root, fresh.files, history.SaveInput{93 message: '${verb}: ${source.id}'94 }) or {95 snapshot.clear_restore_mark(store) or {}96 return refuse(args.json(), err.msg())97 }98 remove := not_in(current, fresh.files)99 report := snapshot.restore_files(store, store.root, snapshot.load(store.root), fresh.files,100 remove) or { return refuse(args.json(), err.msg()) }101 if args.json() {102 emit_json(json2.encode(PluckedJson{103 id: save.id104 source: source.id105 reverse: reverse106 applied: fresh.applied107 written: report.written108 removed: report.removed109 }))110 return 0111 }112 ok('plucked [${source.id}] into [${save.id}]')113 return 0114}115116117118fn plan_pluck(store &storage.Store, source history.Save, reverse bool, filters []string) !(apply.Plan, []snapshot.FileEntry) {119 source_tree := tree_of(store, source.id)!120 mut parent_tree := snapshot.Tree{}121 if source.parent != '' {122 parent_tree = tree_of(store, source.parent)!123 }124 current := working_tree(store)!125 base := if reverse { source_tree } else { parent_tree }126 target := if reverse { parent_tree } else { source_tree }127 plan := apply.three_way(current.files, base.files, target.files, filters, [])128 if apply.conflicts(plan) {129 return error('${plan.conflict} has changed since [${source.id}]; koh does not blend lines — resolve it by hand, or load the save and re-edit')130 }131 snapshot.check_links(store.root, plan.files)!132 return plan, current.files133}134135136fn not_in(before []snapshot.FileEntry, after []snapshot.FileEntry) []string {137 mut kept := map[string]bool{}138 for entry in after {139 kept[entry.path] = true140 }141 mut missing := []string{}142 for entry in before {143 if entry.path !in kept {144 missing << entry.path145 }146 }147 return missing148}149150struct PluckJson {151 source string152 reverse bool153 applied []string154 applied_ok bool155}156157struct PluckedJson {158 id string159 source string160 reverse bool161 applied []string162 written int163 removed int164}