//! `koh migrate` — one-time conversion from Koh 2. module cli import json2 import os import src.fmt import src.machine import src.migrate import src.snapshot import time pub fn cmd_migrate(raw []string) int { args := parse('migrate', raw, Options{ values: ['lane', 'with'] flags: ['json', 'yes'] }) or { return usage_error(err.msg()) } mut lane := args.value('lane') if lane == '' { lane = migrate.default_lane } if lane !in ['dev', 'main'] { return usage_error('--lane takes dev or main, not "${lane}"') } plan := migrate.prepare(os.getwd(), lane, args.values_of('with')) or { return refuse(args.json(), err.msg()) } if !args.json() { 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()}') show_lost(plan.lost) note('this will:') note(' · copy the history into one .koh file and check every save') if plan.renames_build { note(' · rename kepr.build to koh.build, as its own save') } note(' · keep the Koh 2 data as ${migrate.archive_dir}/ — move it back to undo') } approved := confirm(args.flag('yes')) or { return refuse(args.json(), err.msg()) } if !approved { return refuse(args.json(), 'not migrating, then — nothing changed') } report := migrate.run(plan, fn (stage string, done int, total int) { working('${stage} ${done} / ${total} saves') }) or { return refuse(args.json(), err.msg()) } if args.json() { emit_json(json2.encode(MigrateJson{ repo: report.root lane: report.lane saves: report.saves checkpoints: report.checkpoints flags: report.flags objects: report.objects head: report.head ignore_moved: report.ignore_carried build_file: report.build_renamed lost: plan.lost.map(it.path) unsafe_paths: report.unsafe_paths })) return 0 } ok('migrated ${fmt.plural(report.saves, 'save')} from Koh 2 (lane ${report.lane})') note('${fmt.plural(report.checkpoints, 'checkpoint')} · ${fmt.plural(report.flags, 'flag')} · ${fmt.plural(report.objects, 'object')}') warn_unsafe_paths(report.unsafe_paths) note('the Koh 2 data is untouched in ${migrate.archive_dir}/ — move it back to go back') if report.ignore_carried { note('carried the ignore rules to ${snapshot.ignore_file}') } if report.build_renamed { note('renamed kepr.build to koh.build, and saved that') } if report.log_md_was_tracked { warn('LOG.md was tracked by Koh 2; koh writes it itself now, so it is no longer snapshotted') } note('run "koh status" to see what the migration left, then "koh save"') if machine.has_remote() { note('this machine has a remote — run "koh offer" to publish the migrated history') } return 0 } struct MigrateJson { repo string lane string saves int checkpoints int flags int objects int head string ignore_moved bool build_file bool lost []string unsafe_paths []string } // show_lost names each file Koh 2 lost versions of, where they were, and what // can still be done about it before anything is written. fn show_lost(lost []migrate.Lost) { if lost.len == 0 { return } mut paths := []string{} for gone in lost { if gone.path !in paths { paths << gone.path } } warn('Koh 2 has lost ${fmt.plural(lost.len, 'file version')} its saves still list:') for path in paths { versions := lost.filter(it.path == path) mut saves := 0 mut first := versions[0].first mut last := versions[0].last for gone in versions { saves += gone.saves.len first = if gone.first < first { gone.first } else { first } last = if gone.last > last { gone.last } else { last } } mut when := time.unix(first).ymmdd() if time.unix(last).ymmdd() != when { when += ' to ${time.unix(last).ymmdd()}' } note(' ${path} · ${fmt.plural(versions.len, 'version')} · in ${fmt.plural(saves, 'save')} · ${when}') } one := lost.len == 1 them := if one { 'that version' } else { 'any of those versions' } it := if one { 'it' } else { 'them' } note(" if you have ${them} — a backup, Time Machine, another machine's copy —") note(' hand ${it} back and Koh uses ${it} instead: koh migrate --with …') note(' continuing brings those saves over without ${if one { 'it' } else { 'what was lost' }}, each one flagged') }