123module cli45import json26import os7import src.fmt8import src.machine9import src.migrate10import src.snapshot11import time1213pub fn cmd_migrate(raw []string) int {14 args := parse('migrate', raw, Options{15 values: ['lane', 'with']16 flags: ['json', 'yes']17 }) or { return usage_error(err.msg()) }18 mut lane := args.value('lane')19 if lane == '' {20 lane = migrate.default_lane21 }22 if lane !in ['dev', 'main'] {23 return usage_error('--lane takes dev or main, not "${lane}"')24 }25 plan := migrate.prepare(os.getwd(), lane, args.values_of('with')) or { return refuse(args.json(), err.msg()) }26 if !args.json() {27 note('Koh 2 repo · lane ${plan.lane} · ${fmt.plural(plan.saves, 'save')} (${fmt.plural(plan.checkpoints, 'checkpoint')}) · ${fmt.plural(plan.objects, 'object')} · last save ${time.unix(plan.last_saved).ymmdd()}')28 show_lost(plan.lost)29 note('this will:')30 note(' · copy the history into one .koh file and check every save')31 if plan.renames_build {32 note(' · rename kepr.build to koh.build, as its own save')33 }34 note(' · keep the Koh 2 data as ${migrate.archive_dir}/ — move it back to undo')35 }36 approved := confirm(args.flag('yes')) or { return refuse(args.json(), err.msg()) }37 if !approved {38 return refuse(args.json(), 'not migrating, then — nothing changed')39 }40 report := migrate.run(plan, fn (stage string, done int, total int) {41 working('${stage} ${done} / ${total} saves')42 }) or { return refuse(args.json(), err.msg()) }43 if args.json() {44 emit_json(json2.encode(MigrateJson{45 repo: report.root46 lane: report.lane47 saves: report.saves48 checkpoints: report.checkpoints49 flags: report.flags50 objects: report.objects51 head: report.head52 ignore_moved: report.ignore_carried53 build_file: report.build_renamed54 lost: plan.lost.map(it.path)55 unsafe_paths: report.unsafe_paths56 }))57 return 058 }59 ok('migrated ${fmt.plural(report.saves, 'save')} from Koh 2 (lane ${report.lane})')60 note('${fmt.plural(report.checkpoints, 'checkpoint')} · ${fmt.plural(report.flags, 'flag')} · ${fmt.plural(report.objects, 'object')}')61 warn_unsafe_paths(report.unsafe_paths)62 note('the Koh 2 data is untouched in ${migrate.archive_dir}/ — move it back to go back')63 if report.ignore_carried {64 note('carried the ignore rules to ${snapshot.ignore_file}')65 }66 if report.build_renamed {67 note('renamed kepr.build to koh.build, and saved that')68 }69 if report.log_md_was_tracked {70 warn('LOG.md was tracked by Koh 2; koh writes it itself now, so it is no longer snapshotted')71 }72 note('run "koh status" to see what the migration left, then "koh save"')73 if machine.has_remote() {74 note('this machine has a remote — run "koh offer" to publish the migrated history')75 }76 return 077}7879struct MigrateJson {80 repo string81 lane string82 saves int83 checkpoints int84 flags int85 objects int86 head string87 ignore_moved bool88 build_file bool89 lost []string90 unsafe_paths []string91}92939495fn show_lost(lost []migrate.Lost) {96 if lost.len == 0 {97 return98 }99 mut paths := []string{}100 for gone in lost {101 if gone.path !in paths {102 paths << gone.path103 }104 }105 warn('Koh 2 has lost ${fmt.plural(lost.len, 'file version')} its saves still list:')106 for path in paths {107 versions := lost.filter(it.path == path)108 mut saves := 0109 mut first := versions[0].first110 mut last := versions[0].last111 for gone in versions {112 saves += gone.saves.len113 first = if gone.first < first { gone.first } else { first }114 last = if gone.last > last { gone.last } else { last }115 }116 mut when := time.unix(first).ymmdd()117 if time.unix(last).ymmdd() != when {118 when += ' to ${time.unix(last).ymmdd()}'119 }120 note(' ${path} · ${fmt.plural(versions.len, 'version')} · in ${fmt.plural(saves, 'save')} · ${when}')121 }122 one := lost.len == 1123 them := if one { 'that version' } else { 'any of those versions' }124 it := if one { 'it' } else { 'them' }125 note(" if you have ${them} — a backup, Time Machine, another machine's copy —")126 note(' hand ${it} back and Koh uses ${it} instead: koh migrate --with <file> …')127 note(' continuing brings those saves over without ${if one { 'it' } else { 'what was lost' }}, each one flagged')128}