12345678module cli910import json11import src.apply12import src.fmt13import src.history14import src.snapshot15import src.storage1617pub fn cmd_pluck(raw []string) int {18 args := parse('pluck', raw, Options{19 flags: ['reverse', 'dry-run', 'yes', 'json']20 }) or { return usage_error(err.msg()) }21 store := open_store() or { return refuse(args.json(), err.msg()) }22 defer {23 store.close()24 }25 id := args.positional_at(0)26 if id == '' {27 return usage_error('pluck needs a save ID')28 }29 source := history.resolve(store, id) or { return refuse(args.json(), err.msg()) }30 mut filters := []string{}31 if args.positional.len > 1 {32 filters = args.positional[1..]33 }34 35 36 if !args.flag('dry-run') {37 require_clean(store) or { return refuse(args.json(), err.msg()) }38 }39 reverse := args.flag('reverse')40 verb := if reverse { 'pluck reverse' } else { 'pluck' }4142 plan, _ := plan_pluck(store, source, reverse, filters) or {43 return refuse(args.json(), err.msg())44 }4546 if args.flag('dry-run') || plan.applied.len == 0 {47 if args.json() {48 emit_json(json.encode(PluckJson{49 source: source.id50 reverse: reverse51 applied: plan.applied52 applied_ok: true53 }))54 return 055 }56 if plan.applied.len == 0 {57 ok('nothing to apply from [${source.id}]')58 return 059 }60 ok('${verb} [${source.id}] would change:')61 for path in plan.applied {62 println(' ${fmt.amber('~')} ${path}')63 }64 note('${fmt.plural(plan.applied.len, 'file')} — run again without --dry-run to apply')65 return 066 }6768 ok('${verb}: [${source.id}] ${source.message}')69 for path in plan.applied {70 println(' ${fmt.amber('~')} ${path}')71 }72 note('${fmt.plural(plan.applied.len, 'file')} change')73 approved := confirm(args.flag('yes')) or { return refuse(args.json(), err.msg()) }74 if !approved {75 return 176 }77 78 79 require_clean(store) or { return refuse(args.json(), err.msg()) }80 fresh, current := plan_pluck(store, source, reverse, filters) or {81 return refuse(args.json(), err.msg())82 }83 if fresh.applied.len == 0 {84 return refuse(args.json(),85 'nothing to apply from [${source.id}] any more — the tree changed while waiting')86 }87 88 89 snapshot.mark_restore(store) or { return refuse(args.json(), err.msg()) }90 save := history.commit_files(store, store.root, fresh.files, history.SaveInput{91 message: '${verb}: ${source.id}'92 }) or {93 snapshot.clear_restore_mark(store) or {}94 return refuse(args.json(), err.msg())95 }96 remove := not_in(current, fresh.files)97 report := snapshot.restore_files(store, store.root, snapshot.load(store.root), fresh.files,98 remove) or { return refuse(args.json(), err.msg()) }99 if args.json() {100 emit_json(json.encode(PluckedJson{101 id: save.id102 source: source.id103 reverse: reverse104 applied: fresh.applied105 written: report.written106 removed: report.removed107 }))108 return 0109 }110 ok('plucked [${source.id}] into [${save.id}]')111 return 0112}113114115116fn plan_pluck(store &storage.Store, source history.Save, reverse bool, filters []string) !(apply.Plan, []snapshot.FileEntry) {117 source_tree := tree_of(store, source.id)!118 mut parent_tree := snapshot.Tree{}119 if source.parent != '' {120 parent_tree = tree_of(store, source.parent)!121 }122 current := working_tree(store)!123 base := if reverse { source_tree } else { parent_tree }124 target := if reverse { parent_tree } else { source_tree }125 plan := apply.three_way(current.files, base.files, target.files, filters, [])126 if apply.conflicts(plan) {127 return error('${plan.conflict} has changed since [${source.id}]; koh does not merge lines — resolve it by hand, or load the save and re-edit')128 }129 snapshot.check_links(store.root, plan.files)!130 return plan, current.files131}132133134fn not_in(before []snapshot.FileEntry, after []snapshot.FileEntry) []string {135 mut kept := map[string]bool{}136 for entry in after {137 kept[entry.path] = true138 }139 mut missing := []string{}140 for entry in before {141 if entry.path !in kept {142 missing << entry.path143 }144 }145 return missing146}147148struct PluckJson {149 source string150 reverse bool151 applied []string152 applied_ok bool153}154155struct PluckedJson {156 id string157 source string158 reverse bool159 applied []string160 written int161 removed int162}