1234567891011module cli1213import json214import src.fmt15import src.history16import src.machine17import src.snapshot18import src.storage19import src.exchange2021pub fn cmd_strip(raw []string) int {22 args := parse('strip', raw, Options{23 values: ['save']24 flags: ['json']25 }) or { return usage_error(err.msg()) }26 path := args.positional_at(0)27 if path == '' {28 return usage_error('strip needs a path, e.g. koh strip secrets.env')29 }30 31 if !interactive() {32 return refuse(args.json(), 'strip needs a terminal — an agent can never strip')33 }34 store := open_store() or { return refuse(args.json(), err.msg()) }35 defer {36 store.close()37 }38 mut only_save := ''39 if given := args.value_or_none('save') {40 save := history.resolve(store, given) or { return refuse(args.json(), err.msg()) }41 only_save = save.id42 }43 scope := exchange.strip_scope(store, path, only_save) or { return refuse(args.json(), err.msg()) }44 mut local := exchange.LocalStrip{}45 if scope.files == 0 {46 47 48 if !exchange.strip_pending(store, path) {49 ok('nothing in history matches ${path}')50 return 051 }52 note('${path} is already stripped here — finishing the remote')53 } else {54 where := if only_save == '' { 'every save' } else { 'save ${only_save}' }55 println(' ${fmt.red(fmt.glyph_fail)} this cannot be undone — strip is the only command that destroys information')56 println(' ${fmt.amber('!')} ${fmt.plural(scope.files, 'file')} in ${fmt.plural(scope.saves, 'save')}, ${fmt.plural(scope.objects, 'object')} — ${where}')57 println(' ${fmt.dim('strip removes the path from history; the file on disk is left alone')}')58 approved := confirm(false) or { return refuse(args.json(), err.msg()) }59 if !approved {60 note('nothing was stripped')61 return 162 }63 if !typed_confirm('type the path to confirm:', path) {64 note('nothing was stripped — the path did not match')65 return 166 }67 local = exchange.strip_local(store, path, only_save) or {68 return refuse(args.json(), err.msg())69 }70 }71 72 73 push := push_strip_to_remote(store, path)74 75 76 ignored := only_save == '' && (snapshot.add_ignore(store.root, path) or {77 return refuse(args.json(), err.msg())78 })79 if args.json() {80 emit_json(json2.encode(StripJson{81 path: path82 save: only_save83 files_removed: local.files_removed84 objects_dropped: int(local.objects_dropped)85 records_rewritten: push.pushed.records86 remote_objects_dropped: push.pushed.objects87 remote_failed: push.failed88 }))89 return 090 }91 if scope.files > 0 {92 ok('stripped ${path} from ${fmt.plural(local.files_removed, 'file')} across ${fmt.plural(scope.saves, 'save')}')93 note('${local.objects_dropped} objects dropped')94 }95 if !push.skipped {96 if push.failed == '' {97 note('the remote was updated: ${push.pushed.records} save records rewritten, ${push.pushed.objects} objects dropped')98 } else {99 warn('the remote could not be updated: ${push.failed} — run the same strip again to finish it')100 }101 }102 if only_save != '' {103 note('remember to delete ${path} or add it to ${snapshot.ignore_file}')104 } else if ignored {105 note('added /${path} to ${snapshot.ignore_file} — it can never be saved again')106 }107 return 0108}109110struct PushOutcome {111mut:112 pushed exchange.PushReport113 failed string114 skipped bool115}116117118fn push_strip_to_remote(store &storage.Store, path string) PushOutcome {119 if !machine.has_remote() {120 return PushOutcome{121 skipped: true122 }123 }124 mut backend := this_machine_backend() or {125 return PushOutcome{126 failed: err.msg()127 }128 }129 report := exchange.push_strip(store, mut backend, path) or {130 return PushOutcome{131 failed: err.msg()132 }133 }134 return PushOutcome{135 pushed: report136 }137}138139struct StripJson {140 path string141 save string142 files_removed int143 objects_dropped int144 records_rewritten int145 remote_objects_dropped int146 remote_failed string147}