mirror of
https://github.com/discourse/discourse.git
synced 2026-08-06 13:08:40 +08:00
Fixes the following error when generating the upcoming changes status
report
on a weekly basis in GitHub actions:
```
/__w/discourse/discourse/lib/upcoming_changes/status_report.rb:56:in 'UpcomingChanges::StatusReport::Git#capture': git show 45535887f1231b40f8a21b8c154e58cef8670063:plugins/discourse-workflows/config/settings.yml failed: fatal: path 'plugins/discourse-workflows/config/settings.yml' exists on disk, but not in '45535887f1' (RuntimeError)
from /__w/discourse/discourse/lib/upcoming_changes/status_report.rb:47:in 'UpcomingChanges::StatusReport::Git#show_file'
from /__w/discourse/discourse/lib/upcoming_changes/status_report.rb:227:in 'UpcomingChanges::StatusReport::GitHistory#statuses_at'
from /__w/discourse/discourse/lib/upcoming_changes/status_report.rb:215:in 'block in UpcomingChanges::StatusReport::GitHistory#add_history_for_settings_file'
from /__w/discourse/discourse/lib/upcoming_changes/status_report.rb:214:in 'Array#reverse_each'
from /__w/discourse/discourse/lib/upcoming_changes/status_report.rb:214:in 'UpcomingChanges::StatusReport::GitHistory#add_history_for_settings_file'
from /__w/discourse/discourse/lib/upcoming_changes/status_report.rb:205:in 'block in UpcomingChanges::StatusReport::GitHistory#by_change'
from /__w/discourse/discourse/lib/upcoming_changes/status_report.rb:202:in 'Hash#each'
from /__w/discourse/discourse/lib/upcoming_changes/status_report.rb:202:in 'Enumerable#each_with_object'
from /__w/discourse/discourse/lib/upcoming_changes/status_report.rb:202:in 'UpcomingChanges::StatusReport::GitHistory#by_change'
from /__w/discourse/discourse/lib/upcoming_changes/status_report.rb:422:in 'UpcomingChanges::StatusReport#history_by_change'
from /__w/discourse/discourse/lib/upcoming_changes/status_report.rb:363:in 'block in UpcomingChanges::StatusReport#report'
from /__w/discourse/discourse/lib/upcoming_changes/status_report.rb:362:in 'Hash#each'
from /__w/discourse/discourse/lib/upcoming_changes/status_report.rb:362:in 'Enumerable#map'
from /__w/discourse/discourse/lib/upcoming_changes/status_report.rb:362:in 'UpcomingChanges::StatusReport#report'
from /__w/discourse/discourse/lib/upcoming_changes/status_report.rb:499:in 'UpcomingChanges::StatusReport::CLI.run'
from /__w/discourse/discourse/script/upcoming_changes_status_report:9:in '<main>'
```
This happened because we were trying to run `git sho` on a file that was
reverted in a commit. Now, we check whether the file exists for the
specific commit hash before running git show, and continue with the
next valid commit for the file.
254 lines
8.4 KiB
Ruby
Vendored
254 lines
8.4 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
require "fileutils"
|
|
require "open3"
|
|
require "tmpdir"
|
|
|
|
RSpec.describe UpcomingChanges::StatusReport do
|
|
subject(:report) do
|
|
described_class.new(repo_path:, stale_after_days: 14, now: Time.iso8601("2026-05-26T00:00:00Z"))
|
|
end
|
|
|
|
let(:repo_path) { Dir.mktmpdir("upcoming-changes-status-report") }
|
|
let(:commit_shas) { {} }
|
|
|
|
before do
|
|
git("init")
|
|
git("config", "user.name", "Discourse CI")
|
|
git("config", "user.email", "ci@ci.invalid")
|
|
|
|
write_settings(
|
|
"experimental_change" => "experimental",
|
|
"alpha_change" => "alpha",
|
|
"beta_change" => "beta",
|
|
"recent_change" => "alpha",
|
|
"stable_change" => "stable",
|
|
"conceptual_change" => "conceptual",
|
|
"permanent_change" => "permanent",
|
|
"never_change" => "never",
|
|
)
|
|
write_plugin_settings("plugin_alpha_change" => "alpha")
|
|
commit_shas[:original] = commit(
|
|
"FEATURE: Add upcoming changes (#123)",
|
|
date: "2026-04-01T12:00:00Z",
|
|
author_name: "Alice Example",
|
|
author_email: "alice@example.com",
|
|
)
|
|
|
|
remove_plugin_settings
|
|
commit_shas[:plugin_removed] = commit(
|
|
"DEV: Remove plugin settings",
|
|
date: "2026-04-15T12:00:00Z",
|
|
author_name: "Alice Example",
|
|
author_email: "alice@example.com",
|
|
)
|
|
|
|
write_plugin_settings("plugin_alpha_change" => "alpha")
|
|
commit_shas[:plugin_restored] = commit(
|
|
"DEV: Restore plugin settings",
|
|
date: "2026-04-20T12:00:00Z",
|
|
author_name: "Alice Example",
|
|
author_email: "alice@example.com",
|
|
)
|
|
|
|
write_settings(
|
|
"experimental_change" => "experimental",
|
|
"alpha_change" => "alpha",
|
|
"beta_change" => "beta",
|
|
"recent_change" => "beta",
|
|
"stable_change" => "stable",
|
|
"conceptual_change" => "conceptual",
|
|
"permanent_change" => "permanent",
|
|
"never_change" => "never",
|
|
)
|
|
write_plugin_settings("plugin_alpha_change" => "alpha")
|
|
commit_shas[:recent] = commit(
|
|
"DEV: Bump recent upcoming change (#456)",
|
|
date: "2026-05-20T12:00:00Z",
|
|
author_name: "Bob Example",
|
|
author_email: "bob@example.com",
|
|
)
|
|
end
|
|
|
|
after { FileUtils.remove_entry(repo_path) }
|
|
|
|
describe "#report" do
|
|
it "reports eligibility and git metadata", :aggregate_failures do
|
|
records = report.report.index_by { |record| record.fetch(:name) }
|
|
|
|
expect(records.fetch("experimental_change")).to include(
|
|
settings_path: "config/site_settings.yml",
|
|
current_status: "experimental",
|
|
next_status: "alpha",
|
|
eligible: true,
|
|
eligibility_reason: "status_unchanged_for_14_days",
|
|
last_status_change_commit: commit_shas[:original],
|
|
original_commit: commit_shas[:original],
|
|
original_author_name: "Alice Example",
|
|
original_author_email: "alice@example.com",
|
|
original_pr_number: "123",
|
|
branch: "dev/upcoming-change-status-bump/experimental_change",
|
|
title: "FEATURE: Bump experimental_change upcoming change to alpha",
|
|
pr_label: "upcoming-change",
|
|
)
|
|
expect(records.fetch("experimental_change").fetch(:pr_body)).to include(
|
|
"<!-- upcoming-change-status-pr:experimental_change -->",
|
|
"This automated PR moves `experimental_change` from `experimental` to `alpha`",
|
|
"Original PR: #123",
|
|
)
|
|
expect(records.fetch("alpha_change")).to include(
|
|
current_status: "alpha",
|
|
next_status: "beta",
|
|
eligible: true,
|
|
)
|
|
expect(records.fetch("beta_change")).to include(
|
|
current_status: "beta",
|
|
next_status: "stable",
|
|
eligible: true,
|
|
)
|
|
recent_change = records.fetch("recent_change")
|
|
expect(recent_change).to include(
|
|
current_status: "beta",
|
|
next_status: "stable",
|
|
eligible: false,
|
|
eligibility_reason: "status_changed_recently",
|
|
last_status_change_commit: commit_shas[:recent],
|
|
days_since_status_change: 5,
|
|
)
|
|
expect(Time.iso8601(recent_change.fetch(:last_status_change_date))).to eq(
|
|
Time.iso8601("2026-05-20T12:00:00Z"),
|
|
)
|
|
expect(records.fetch("stable_change")).to include(
|
|
current_status: "stable",
|
|
next_status: nil,
|
|
eligible: false,
|
|
eligibility_reason: "terminal_status",
|
|
)
|
|
expect(records.fetch("conceptual_change")).to include(
|
|
current_status: "conceptual",
|
|
eligible: false,
|
|
eligibility_reason: "terminal_status",
|
|
)
|
|
expect(records.fetch("permanent_change")).to include(
|
|
current_status: "permanent",
|
|
eligible: false,
|
|
eligibility_reason: "terminal_status",
|
|
)
|
|
expect(records.fetch("never_change")).to include(
|
|
current_status: "never",
|
|
eligible: false,
|
|
eligibility_reason: "terminal_status",
|
|
)
|
|
expect(records.fetch("plugin_alpha_change")).to include(
|
|
settings_path: "plugins/chat/config/settings.yml",
|
|
current_status: "alpha",
|
|
next_status: "beta",
|
|
eligible: true,
|
|
original_commit: commit_shas[:original],
|
|
last_status_change_commit: commit_shas[:original],
|
|
)
|
|
end
|
|
end
|
|
|
|
describe "#apply" do
|
|
it "updates only the target setting file" do
|
|
report.apply("plugin_alpha_change")
|
|
|
|
metadata =
|
|
described_class::MetadataLoader.from_file(
|
|
File.join(repo_path, "plugins/chat/config/settings.yml"),
|
|
strict: true,
|
|
)
|
|
|
|
expect(metadata.transform_values { |value| value[:status].to_s }).to include(
|
|
plugin_alpha_change: "beta",
|
|
)
|
|
expect(File.read(File.join(repo_path, "config/site_settings.yml"))).to include(
|
|
" alpha_change:\n default: false\n client: true\n hidden: true\n upcoming_change:\n status: alpha\n",
|
|
)
|
|
end
|
|
end
|
|
|
|
describe described_class::SourceStatusUpdater do
|
|
it "raises when the status line cannot be edited" do
|
|
settings_file = File.join(repo_path, "config/site_settings.yml")
|
|
File.write(settings_file, <<~YAML)
|
|
experimental:
|
|
malformed_change:
|
|
upcoming_change:
|
|
status: "alpha beta"
|
|
YAML
|
|
|
|
updater = described_class.new(settings_file:)
|
|
|
|
expect {
|
|
updater.update!(change_name: "malformed_change", next_status: "beta")
|
|
}.to raise_error(RuntimeError, /Could not parse status line/)
|
|
end
|
|
end
|
|
|
|
def git(*args, env: {})
|
|
stdout, stderr, status = Open3.capture3(env, "git", "-C", repo_path, *args)
|
|
raise "git #{args.join(" ")} failed: #{stderr}" if !status.success?
|
|
|
|
stdout
|
|
end
|
|
|
|
def commit(message, date:, author_name:, author_email:)
|
|
git("add", ".")
|
|
git(
|
|
"commit",
|
|
"-m",
|
|
message,
|
|
env: {
|
|
"GIT_AUTHOR_DATE" => date,
|
|
"GIT_COMMITTER_DATE" => date,
|
|
"GIT_AUTHOR_NAME" => author_name,
|
|
"GIT_AUTHOR_EMAIL" => author_email,
|
|
"GIT_COMMITTER_NAME" => "Discourse CI",
|
|
"GIT_COMMITTER_EMAIL" => "ci@ci.invalid",
|
|
},
|
|
)
|
|
git("rev-parse", "HEAD").strip
|
|
end
|
|
|
|
def write_settings(statuses)
|
|
FileUtils.mkdir_p(File.join(repo_path, "config"))
|
|
File.write(File.join(repo_path, "config/site_settings.yml"), "experimental:\n")
|
|
|
|
File.open(File.join(repo_path, "config/site_settings.yml"), "a") do |file|
|
|
statuses.each do |name, status|
|
|
file.write(" #{name}:\n")
|
|
file.write(" default: false\n")
|
|
file.write(" client: true\n")
|
|
file.write(" hidden: true\n")
|
|
file.write(" upcoming_change:\n")
|
|
file.write(" status: #{status}\n")
|
|
file.write(" impact: \"feature,all_members\"\n")
|
|
file.write(" learn_more_url: \"https://meta.discourse.org/t/-/123\"\n")
|
|
end
|
|
end
|
|
end
|
|
|
|
def write_plugin_settings(statuses)
|
|
FileUtils.mkdir_p(File.join(repo_path, "plugins/chat/config"))
|
|
File.write(File.join(repo_path, "plugins/chat/config/settings.yml"), "chat:\n")
|
|
|
|
File.open(File.join(repo_path, "plugins/chat/config/settings.yml"), "a") do |file|
|
|
statuses.each do |name, status|
|
|
file.write(" #{name}:\n")
|
|
file.write(" default: false\n")
|
|
file.write(" client: true\n")
|
|
file.write(" hidden: true\n")
|
|
file.write(" upcoming_change:\n")
|
|
file.write(" status: #{status}\n")
|
|
file.write(" impact: \"feature,all_members\"\n")
|
|
file.write(" learn_more_url: \"https://meta.discourse.org/t/-/123\"\n")
|
|
end
|
|
end
|
|
end
|
|
|
|
def remove_plugin_settings
|
|
FileUtils.rm_rf(File.join(repo_path, "plugins/chat"))
|
|
end
|
|
end
|