b760c74f453b
check release output paths before any build runs, and pin the duplicate platform, the missing --notes value and the unchanged migration in tests (3.0.84)
- time
- 2026-09-27 10:37 UTC · 2 weeks ago
- machine
- Junes-MacBook-Pro.local
- agent
- deepseek/deepseek-flash
- session
- koh 3 phases 6-8
- parent
- 1278e421b5fc
↓ koh-b760c74f.tar.gzatlas at this save
files changed7 files
VERSION+1 −1src/build/build_test.v+1 −0src/migrate/migrate_test.v+16 −0src/release/release.v+11 −8src/release/release_test.v+30 −0tests/cli_test.v+4 −0v.mod+1 −1src/build/build_test.v+1 −0
@@ -192,6 +192,7 @@192192 ['output = a\nplatforms = macos-aarch64\n', 'has no command'],193193 ['command = true\noutput = a\n', 'names no platforms'],194194 ['command = true\noutput = a\nplatforms = macos-aarch64, amiga\n', 'amiga'],195+ ['command = true\noutput = a\nplatforms = macos-aarch64, macos-aarch64\n', 'twice'],195196 ['command = true\noutput = a\nplatforms = macos-aarch64\nlang = cobol\n', 'cobol'],196197 ]197198 for case in cases {src/migrate/migrate_test.v+16 −0
@@ -227,6 +227,22 @@227227 assert !report.build_renamed228228 assert !os.exists(os.join_path(fixture.dir, 'koh.build'))229229 assert os.is_dir(os.join_path(fixture.dir, '.koh-v2'))230+ store := storage.open(fixture.dir)!231+ defer {232+ store.close()233+ }234+ head := history.head(store) or { panic('no head after migrating') }235+ // The migration carries Koh 2's ignore rules to a root .kohignore, so the236+ // tree differs from the imported head by exactly that file — which is what237+ // the report tells the operator to save.238+ saved := history.files_of(store, head.id)!239+ mut unsaved := []string{}240+ for file in snapshot.walk(store, fixture.dir, snapshot.load(fixture.dir))! {241+ if !saved.any(it.path == file.path) {242+ unsaved << file.path243+ }244+ }245+ assert unsaved == [snapshot.ignore_file]230246 }231247 232248 fn test_migrate_refuses_a_tree_that_does_not_match_the_last_save() {src/release/release.v+11 −8
@@ -68,17 +68,20 @@6868 pub fn publish(store &storage.Store, mut backend remote.Backend, repo string,6969 config build.Config, version string, notes string) !Published {7070 head := history.head(store) or { return error(exchange.no_saves) }71- mut built := []build.Built{}71+ // Every build runs before the first upload, so two platforms landing on one72+ // path would publish the second build's bytes for both. This is checked73+ // before anything runs, so a collision builds nothing at all.7274 mut paths := map[string]bool{}7375 for platform in config.platforms {74- artifact := build.build_one(store.root, config, platform, version)!75- // Every build happens before the first upload, so two platforms landing76- // on one path would publish the second build's bytes for both.77- if artifact.path in paths {78- return error('two platforms build to the same file, ${artifact.path} — name the platform in ${build.config_name}\'s output, as "out/demo-{platform}" does')76+ path := build.output_path(config, platform, version)77+ if path in paths {78+ return error('two platforms build to the same file, ${path} — name the platform in ${build.config_name}\'s output, as "out/demo-{platform}" does')7979 }80- paths[artifact.path] = true81- built << artifact80+ paths[path] = true81+ }82+ mut built := []build.Built{}83+ for platform in config.platforms {84+ built << build.build_one(store.root, config, platform, version)!8285 }8386 mut files := []File{}8487 for artifact in built {src/release/release_test.v+30 −0
@@ -124,6 +124,36 @@124124 assert backend.get(file_key('proj', '1.2.3', 'linux-aarch64', 'demo-1.2.3-linux-aarch64'))!.found125125 }126126 127+// Two platforms that build to one file are refused before anything runs: no128+// build, no upload, and the bucket is untouched.129+fn test_two_platforms_building_to_one_file_are_refused() {130+ base := scratch('collision')!131+ defer {132+ os.rmdir_all(base) or {}133+ }134+ mut store := open_repo('collision')!135+ defer {136+ store.close()137+ }138+ write(store.root, 'VERSION', '1.2.3\n')!139+ // No {platform}, so both platforms would write the same file.140+ write(store.root, 'koh.build', 'command = mkdir -p out && printf koh > out/demo141+output = out/demo142+platforms = macos-aarch64, linux-aarch64143+')!144+ config := build.read_config(store.root)!145+ make_save(store, 'first')!146+ mut backend := remote.new_folder(os.join_path(base, 'remote'))!147+148+ if _ := publish(store, mut backend, 'proj', config, '1.2.3', '') {149+ assert false, 'two platforms building to one file must be refused'150+ } else {151+ assert err.msg().contains('two platforms build to the same file')152+ }153+ assert !os.exists(os.join_path(store.root, 'out'))154+ assert backend.list('proj/releases')!.len == 0155+}156+127157 // A published version never changes: a second build with different bytes for128158 // the same platform is refused before anything is uploaded.129159 fn test_a_different_file_for_a_published_version_is_refused() {tests/cli_test.v+4 −0
@@ -812,6 +812,10 @@812812 notes := 'a release note long enough to clear forty characters and the twelve word rule easily'813813 assert run(dir, 'offer', '--notes', notes).code == 2814814 assert run(dir, 'offer', '--notes', '').code == 2815+ // A value-taking option with nothing after it is a usage error too.816+ missing := run(dir, 'offer', '--notes')817+ assert missing.code == 2818+ assert missing.err.contains('needs a value')815819 assert run(dir, 'offer', 'extra').code == 2816820 817821 write(dir, 'VERSION', '1.2.3\n')v.mod+1 −1
@@ -1,6 +1,6 @@11 Module {22 name: 'koh3'3- version: '3.0.83'3+ version: '3.0.84'44 description: 'One SQLite file per repo, one line of history.'55 license: 'MIT'66 }