1234567891011module cli1213import json214import src.fmt15import src.history16import src.machine17import src.snapshot18import src.storage19import src.exchange202122pub 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 sent := send_strip_to_remote(store, path)75 76 77 ignored := only_save == '' && (snapshot.add_ignore(store.root, path) or {78 return refuse(args.json(), err.msg())79 })80 if args.json() {81 emit_json(json2.encode(StripJson{82 path: path83 save: only_save84 files_removed: local.files_removed85 objects_dropped: int(local.objects_dropped)86 records_rewritten: sent.remote.records87 remote_objects_dropped: sent.remote.objects88 remote_failed: sent.failed89 }))90 return 091 }92 if scope.files > 0 {93 ok('stripped ${path} from ${fmt.plural(local.files_removed, 'file')} across ${fmt.plural(scope.saves, 'save')}')94 note('${fmt.plural(int(local.objects_dropped), 'object')} dropped')95 }96 if !sent.skipped {97 if sent.failed == '' {98 note('the remote was updated: ${fmt.plural(sent.remote.records, 'save record')} rewritten, ${fmt.plural(sent.remote.objects, 'object')} dropped')99 } else {100 warn('the remote could not be updated: ${sent.failed} — run the same strip again to finish it')101 }102 }103 if only_save != '' {104 note('remember to delete ${path} or add it to ${snapshot.ignore_file}')105 } else if ignored {106 note('added /${path} to ${snapshot.ignore_file} — it can never be saved again')107 }108 return 0109}110111struct SendOutcome {112mut:113 remote exchange.SendReport114 failed string115 skipped bool116}117118119fn send_strip_to_remote(store &storage.Store, path string) SendOutcome {120 if !machine.has_remote() {121 return SendOutcome{122 skipped: true123 }124 }125 mut backend := watched_backend('updating the remote') or {126 return SendOutcome{127 failed: err.msg()128 }129 }130 report := exchange.send_strip(store, mut backend, path) or {131 return SendOutcome{132 failed: err.msg()133 }134 }135 return SendOutcome{136 remote: report137 }138}139140struct StripJson {141 path string142 save string143 files_removed int144 objects_dropped int145 records_rewritten int146 remote_objects_dropped int147 remote_failed string148}