123456789101112module cli1314import json215import src.fmt16import src.history17import src.machine18import src.snapshot19import src.storage20import src.exchange2122pub fn cmd_strip(raw []string) int {23 args := parse('strip', raw, Options{24 values: ['save']25 flags: ['json']26 }) or { return usage_error(err.msg()) }27 path := args.positional_at(0)28 if path == '' {29 return usage_error('strip needs a path, e.g. koh strip secrets.env')30 }31 32 if !interactive() {33 return refuse(args.json(), 'strip needs a terminal — an agent can never strip')34 }35 store := open_store() or { return refuse(args.json(), err.msg()) }36 defer {37 store.close()38 }39 mut only_save := ''40 if given := args.value_or_none('save') {41 save := history.resolve(store, given) or { return refuse(args.json(), err.msg()) }42 only_save = save.id43 }44 scope := exchange.strip_scope(store, path, only_save) or { return refuse(args.json(), err.msg()) }45 mut local := exchange.LocalStrip{}46 if scope.files == 0 {47 48 49 if !exchange.strip_pending(store, path) {50 ok('nothing in history matches ${path}')51 return 052 }53 note('${path} is already stripped here — finishing the remote')54 } else {55 where := if only_save == '' { 'every save' } else { 'save ${only_save}' }56 println(' ${fmt.red(fmt.glyph_fail)} this cannot be undone — strip is the only command that destroys information')57 println(' ${fmt.amber('!')} ${fmt.plural(scope.files, 'file')} in ${fmt.plural(scope.saves, 'save')}, ${fmt.plural(scope.objects, 'object')} — ${where}')58 println(' ${fmt.dim('strip removes the path from history; the file on disk is left alone')}')59 approved := confirm(false) or { return refuse(args.json(), err.msg()) }60 if !approved {61 note('nothing was stripped')62 return 163 }64 if !typed_confirm('type the path to confirm:', path) {65 note('nothing was stripped — the path did not match')66 return 167 }68 local = exchange.strip_local(store, path, only_save) or {69 return refuse(args.json(), err.msg())70 }71 }72 73 74 push := push_strip_to_remote(store, path)75 if args.json() {76 emit_json(json2.encode(StripJson{77 path: path78 save: only_save79 files_removed: local.files_removed80 objects_dropped: int(local.objects_dropped)81 records_rewritten: push.pushed.records82 remote_objects_dropped: push.pushed.objects83 remote_failed: push.failed84 }))85 return 086 }87 if scope.files > 0 {88 ok('stripped ${path} from ${fmt.plural(local.files_removed, 'file')} across ${fmt.plural(scope.saves, 'save')}')89 note('${local.objects_dropped} objects dropped')90 }91 if !push.skipped {92 if push.failed == '' {93 note('the remote was updated: ${push.pushed.records} save records rewritten, ${push.pushed.objects} objects dropped')94 } else {95 warn('the remote could not be updated: ${push.failed} — run the same strip again to finish it')96 }97 }98 note('remember to delete ${path} or add it to ${snapshot.ignore_file}')99 return 0100}101102struct PushOutcome {103mut:104 pushed exchange.PushReport105 failed string106 skipped bool107}108109110fn push_strip_to_remote(store &storage.Store, path string) PushOutcome {111 if !machine.has_remote() {112 return PushOutcome{113 skipped: true114 }115 }116 mut backend := this_machine_backend() or {117 return PushOutcome{118 failed: err.msg()119 }120 }121 report := exchange.push_strip(store, mut backend, path) or {122 return PushOutcome{123 failed: err.msg()124 }125 }126 return PushOutcome{127 pushed: report128 }129}130131struct StripJson {132 path string133 save string134 files_removed int135 objects_dropped int136 records_rewritten int137 remote_objects_dropped int138 remote_failed string139}