|
4 | 4 | # Copyright, 2026, by Samuel Williams. |
5 | 5 |
|
6 | 6 | require_relative "project" |
| 7 | +require_relative "backup" |
7 | 8 | require "bake/releases" |
8 | 9 | require "digest" |
9 | 10 | require "net/http" |
@@ -35,9 +36,20 @@ def build(number) |
35 | 36 | guard_release(release, evidence.fetch(:commit)) |
36 | 37 | filename = "#{evidence.fetch(:name)}-#{evidence.fetch(:version)}.gem" |
37 | 38 | files = release_files(file: filename) |
38 | | - names = release.fetch("assets").map{|asset| asset.fetch("name")} |
39 | | - raise "Retained release is incomplete; restore the original files before retrying." unless files.all?{|file| names.include?(File.basename(file))} |
40 | | - system("gh", "release", "download", release.fetch("tag_name"), "--repo", @repository, "--dir", path, *files.flat_map{|file| ["--pattern", File.basename(file)]}, chdir: @root) |
| 39 | + if backup = release.fetch("assets").find{|asset| asset.fetch("name") == "release.tar"} |
| 40 | + contents = read_backup(release, backup, files) |
| 41 | + contents.each do |name, content| |
| 42 | + file = File.join(path, name) |
| 43 | + raise "Existing artifact differs: #{name}" if File.exist?(file) && File.binread(file) != content |
| 44 | + end |
| 45 | + contents.each do |name, content| |
| 46 | + File.binwrite(File.join(path, name), content) |
| 47 | + end |
| 48 | + else |
| 49 | + names = release.fetch("assets").map{|asset| asset.fetch("name")} |
| 50 | + raise "Retained release is incomplete; restore the original files before retrying." unless files.all?{|file| names.include?(File.basename(file))} |
| 51 | + system("gh", "release", "download", release.fetch("tag_name"), "--repo", @repository, "--dir", path, *files.flat_map{|file| ["--pattern", File.basename(file)]}, chdir: @root) |
| 52 | + end |
41 | 53 | elsif retained |
42 | 54 | raise "Retained artifact expired and no GitHub release is available. Restore the original files before retrying." |
43 | 55 | else |
@@ -161,16 +173,37 @@ def preserve_release(receipt) |
161 | 173 | end |
162 | 174 | guard_release(release, receipt.fetch(:commit)) |
163 | 175 | assets = release.fetch("assets") |
164 | | - release_files(receipt).each do |file| |
| 176 | + files = release_files(receipt) |
| 177 | + files.each do |file| |
165 | 178 | if existing = assets.find{|asset| asset.fetch("name") == File.basename(file)} |
166 | 179 | raise "Existing release asset differs: #{file}" unless existing.fetch("digest") == "sha256:#{Digest::SHA256.file(file).hexdigest}" |
167 | | - else |
| 180 | + end |
| 181 | + end |
| 182 | + if backup = assets.find{|asset| asset.fetch("name") == "release.tar"} |
| 183 | + contents = read_backup(release, backup, files) |
| 184 | + raise "Existing release backup differs." unless files.all?{|file| contents.fetch(File.basename(file)) == File.binread(file)} |
| 185 | + else |
| 186 | + # A complete backup needs only one successful upload, before individual assets: |
| 187 | + backup = File.join(@root, "pkg/release.tar") |
| 188 | + Backup.write(backup, files) |
| 189 | + system("gh", "release", "upload", tag, backup, "--repo", @repository, chdir: @root) |
| 190 | + end |
| 191 | + files.each do |file| |
| 192 | + unless assets.any?{|asset| asset.fetch("name") == File.basename(file)} |
168 | 193 | system("gh", "release", "upload", tag, file, "--repo", @repository, chdir: @root) |
169 | 194 | end |
170 | 195 | end |
171 | 196 | release |
172 | 197 | end |
173 | 198 |
|
| 199 | + def read_backup(release, asset, files) |
| 200 | + Tempfile.create("release-backup") do |file| |
| 201 | + system("gh", "release", "download", release.fetch("tag_name"), "--repo", @repository, "--pattern", "release.tar", "--output", file.path, "--clobber", chdir: @root) |
| 202 | + raise "Release backup digest mismatch." unless asset.fetch("digest") == "sha256:#{Digest::SHA256.file(file.path).hexdigest}" |
| 203 | + Backup.read(file.path, files.map{|path| File.basename(path)}) |
| 204 | + end |
| 205 | + end |
| 206 | + |
174 | 207 | def verify_registry(receipt, bundle, attempts: 7, delay: 10) |
175 | 208 | local_bundle = JSON.parse(File.read(bundle)) |
176 | 209 | attempts.times do |attempt| |
|
0 commit comments