cmd_offer.v

4.7 KB · raw

1//! `koh offer` — send the local saves to this machine's remote, and, with2//! `--builds`, publish a release built from them.34module cli56import json27import os8import src.build9import src.exchange10import src.fmt11import src.history12import src.machine13import src.release14import src.remote15import src.storage1617pub fn cmd_offer(raw []string) int {18	args := parse('offer', raw, Options{19		values: ['notes']20		flags:  ['json', 'builds']21	}) or { return usage_error(err.msg()) }22	if args.value('notes') != '' && !args.flag('builds') {23		return usage_error('--notes belongs to --builds')24	}25	store := open_store() or { return refuse(args.json(), err.msg()) }26	defer {27		store.close()28	}29	mut backend := this_machine_backend() or { return refuse(args.json(), err.msg()) }30	if args.flag('builds') {31		return offer_builds(args, store, mut backend)32	}33	report := exchange.offer(store, mut backend) or { return refuse(args.json(), err.msg()) }34	if args.json() {35		emit_json(json2.encode(OfferedJson{36			repo:    report.repo37			saves:   report.saves38			objects: report.objects39			events:  report.events40			head:    report.head41		}))42		return 043	}44	if report.saves + report.objects + report.events == 0 {45		ok('the remote already has everything from [${report.head}]')46		return 047	}48	ok('offered ${report.saves} saves · ${report.objects} objects · ${report.events} events to "${report.repo}"')49	note('remote head is now [${report.head}]')50	return 051}5253// offer_builds is `koh offer --builds`: the history goes up first, then a54// release built from the working tree. Unsaved changes are refused, so the55// published files always belong to a save that is on the remote.56fn offer_builds(args Args, store &storage.Store, mut backend remote.Backend) int {57	head := history.head(store) or { return refuse(args.json(), exchange.no_saves) }58	if exchange.dirty(store, head) or { true } {59		return refuse(args.json(), 'save or checkpoint first — a release is built from a save')60	}61	report := exchange.offer(store, mut backend) or { return refuse(args.json(), err.msg()) }62	config := build.read_config(store.root) or { return refuse(args.json(), err.msg()) }63	// Not `version`: that is the koh binary's own version, in this module.64	release_version := build.project_version(store.root) or {65		return refuse(args.json(), err.msg())66	}67	notes := args.value('notes')68	release.check_notes(notes) or { return refuse(args.json(), err.msg()) }69	published := release.publish(store, mut backend, report.repo, config, release_version, notes) or {70		return refuse(args.json(), err.msg())71	}72	if args.json() {73		emit_json(json2.encode(PublishedJson{74			repo:    report.repo75			version: published.version76			files:   published.files.map(PublishedFileJson{77				platform: it.platform78				name:     it.name79				size:     it.size80				blake3:   it.blake381			})82		}))83		return 084	}85	for file in published.files {86		ok('${file.platform}  ${file.name}  (${fmt.human_bytes(file.size)})')87	}88	ok('published ${published.version} of "${report.repo}" from [${report.head}]')89	return 090}9192// this_machine_backend builds the backend from the machine's saved remote.93pub fn this_machine_backend() !remote.Backend {94	settings := machine.load_remote()!95	if settings.kind == machine.kind_folder {96		// Only `koh remote` creates the folder. Missing later, it is usually a97		// disk or share that is not mounted, and a stand-in folder would take98		// saves the other machine never sees.99		if !os.is_dir(settings.path) {100			return error('the remote folder ${settings.path} is not there — is its disk mounted? ("koh remote" changes it)')101		}102		return remote.new_folder(settings.path)!103	}104	credentials := machine.load_credentials()!105	return remote.new_s3(settings.endpoint, settings.bucket, settings.region,106		credentials.access_key, credentials.secret_key)!107}108109// auto_offer runs after every save and checkpoint. A remote that is not set up,110// or not reachable, must never fail the save: it prints one line and stops.111// `quiet` is for `--json`, where that line would land inside the document.112pub fn auto_offer(store &storage.Store, quiet bool) {113	if !machine.has_remote() {114		return115	}116	mut backend := this_machine_backend() or {117		if !quiet {118			note('not offered: ${err.msg()}')119		}120		return121	}122	report := exchange.offer(store, mut backend) or {123		if !quiet {124			note('not offered: ${err.msg()}')125		}126		return127	}128	if !quiet && report.saves + report.objects + report.events > 0 {129		note('offered to "${report.repo}" — ${report.saves} saves · ${report.objects} objects')130	}131}132133struct OfferedJson {134	repo    string135	saves   int136	objects int137	events  int138	head    string139}140141struct PublishedFileJson {142	platform string143	name     string144	size     i64145	blake3   string146}147148struct PublishedJson {149	repo    string150	version string151	files   []PublishedFileJson152}