123456789101112module cli1314import json15import src.fmt16import src.history17import src.machine18import src.snapshot19import src.storage20import src.sync2122pub 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 := sync.strip_scope(store, path, only_save) or { return refuse(args.json(), err.msg()) }45 mut local := sync.LocalStrip{}46 if scope.files == 0 {47 48 49 if !sync.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,58 'save')}, ${fmt.plural(scope.objects, 'object')} — ${where}')59 println(' ${fmt.dim('strip removes the path from history; the file on disk is left alone')}')60 approved := confirm(false) or { return refuse(args.json(), err.msg()) }61 if !approved {62 note('nothing was stripped')63 return 164 }65 if !typed_confirm('type the path to confirm:', path) {66 note('nothing was stripped — the path did not match')67 return 168 }69 local = sync.strip_local(store, path, only_save) or {70 return refuse(args.json(), err.msg())71 }72 }73 74 75 push := push_strip_to_remote(store, path)76 if args.json() {77 emit_json(json.encode(StripJson{78 path: path79 save: only_save80 files_removed: local.files_removed81 objects_dropped: int(local.objects_dropped)82 records_rewritten: push.pushed.records83 remote_objects_dropped: push.pushed.objects84 remote_failed: push.failed85 }))86 return 087 }88 if scope.files > 0 {89 ok('stripped ${path} from ${fmt.plural(local.files_removed, 'file')} across ${fmt.plural(scope.saves,90 'save')}')91 note('${local.objects_dropped} objects dropped')92 }93 if !push.skipped {94 if push.failed == '' {95 note('the remote was updated: ${push.pushed.records} save records rewritten, ${push.pushed.objects} objects dropped')96 } else {97 warn('the remote could not be updated: ${push.failed} — run the same strip again to finish it')98 }99 }100 note('remember to delete ${path} or add it to ${snapshot.ignore_file}')101 return 0102}103104struct PushOutcome {105mut:106 pushed sync.PushReport107 failed string108 skipped bool109}110111112fn push_strip_to_remote(store &storage.Store, path string) PushOutcome {113 if !machine.has_remote() {114 return PushOutcome{115 skipped: true116 }117 }118 mut backend := this_machine_backend() or { return PushOutcome{119 failed: err.msg()120 } }121 report := sync.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}