diff --git a/app/models/report.rb b/app/models/report.rb index 968581a96..a6275c642 100644 --- a/app/models/report.rb +++ b/app/models/report.rb @@ -7,19 +7,29 @@ class Report < ApplicationRecord belongs_to :server validates :server_id, :presence => true - validates :revision, :numericality => { :only_integer => true }, allow_nil: true + # SVN revision number or full 40-hex git commit SHA + validates :revision, :format => { :with => /\A(?:\d+|\h{40})\z/ }, allow_nil: true validates :datetime, :uniqueness => { :scope => [:server_id, :branch] } validates :branch, :presence => true validates :summary, :presence => true + def self.extract_full_sha(ltsv) + return nil unless ltsv + ltsv[%r<"https\\x3A//github.com/ruby/ruby":(\h{40})>, 1] || + ltsv[%r, 1] + end + def sha1 - !ltsv ? nil : \ - ltsv[%r<"https\\x3A//github.com/ruby/ruby":([^\t]+)>, 1] || - ltsv[%r, 1] + if revision&.match?(/\A\h{40}\z/) + revision + elsif ltsv + ltsv[%r<"https\\x3A//github.com/ruby/ruby":([^\t]+)>, 1] || + ltsv[%r, 1] + end end def revisionuri - if revision + if revision&.match?(/\A\d+\z/) "https://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=#{revision}" elsif sha1 "https://github.com/ruby/ruby/commit/#{sha1}" @@ -156,8 +166,9 @@ def self.scan_recent_ltsv(server, depsuffixed_name, body) end ary.reverse_each do |line, h, dt, datetime| puts "reporting #{server.name} #{depsuffixed_name} #{dt} ..." - # subversion revision of ruby is less than 99999 - revision = h["ruby_rev"] && h["ruby_rev"].size <= 6 ? h["ruby_rev"][1, 5].to_i : nil + # git commit SHA if present, otherwise subversion revision (less than 99999) + revision = extract_full_sha(line) || + (h["ruby_rev"] && h["ruby_rev"].size <= 6 ? h["ruby_rev"][1, 5] : nil) summary = h["title"] summary << ' success' if h["result"] == 'success' diff = h["different_sections"] diff --git a/app/views/reports/index.html.erb b/app/views/reports/index.html.erb index b2e557bcf..6027b6417 100644 --- a/app/views/reports/index.html.erb +++ b/app/views/reports/index.html.erb @@ -34,7 +34,7 @@ diffuri = report.diffuri - if report.revision + if report.revision&.match?(/\A\d+\z/) revision = "r#{report.revision}" elsif report.sha1 revision = report.sha1[0, 11] @@ -108,7 +108,7 @@ <%= link_to report.sjstdt, report.loguri, title: report.jstdt %> <%= link_to report.branch, report.recenturi %> <%= report.option %> - <%= report.patchlevel + " " if report.patchlevel %><%= report.revision %> + <%= report.patchlevel + " " if report.patchlevel %><%= report.revision&.match?(/\A\d+\z/) ? report.revision : report.revision.to_s[0, 11] %> <% if report.build %> <%= report.build %> <% else %> diff --git a/db/migrate/20260727051843_change_reports_revision_to_string.rb b/db/migrate/20260727051843_change_reports_revision_to_string.rb new file mode 100644 index 000000000..45ec20f6d --- /dev/null +++ b/db/migrate/20260727051843_change_reports_revision_to_string.rb @@ -0,0 +1,27 @@ +class ChangeReportsRevisionToString < ActiveRecord::Migration[8.1] + def up + if postgresql? + change_column :reports, :revision, :string, using: 'revision::text' + # varchar_pattern_ops is required for LIKE 'abc1234%' prefix searches + add_index :reports, :revision, opclass: :varchar_pattern_ops + else + change_column :reports, :revision, :string + add_index :reports, :revision + end + end + + def down + remove_index :reports, :revision + if postgresql? + change_column :reports, :revision, :integer, using: 'revision::integer' + else + change_column :reports, :revision, :integer + end + end + + private + + def postgresql? + connection.adapter_name.match?(/postgresql/i) + end +end diff --git a/db/schema.rb b/db/schema.rb index 7a119c258..d652c675d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.1].define(version: 2024_11_21_042607) do +ActiveRecord::Schema[8.1].define(version: 2026_07_27_051843) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" @@ -67,12 +67,13 @@ t.datetime "datetime", precision: nil t.text "ltsv" t.string "option" - t.integer "revision" + t.string "revision" t.integer "server_id" t.text "summary" t.datetime "updated_at", precision: nil t.index ["branch"], name: "index_reports_on_branch" t.index ["datetime"], name: "index_reports_on_datetime" + t.index ["revision"], name: "index_reports_on_revision", opclass: :varchar_pattern_ops t.index ["server_id", "branch", "option"], name: "index_reports_on_server_id_and_branch_and_option" end diff --git a/lib/tasks/backfill_revision.rake b/lib/tasks/backfill_revision.rake new file mode 100644 index 000000000..eb5543c00 --- /dev/null +++ b/lib/tasks/backfill_revision.rake @@ -0,0 +1,20 @@ +namespace :reports do + desc "Backfill reports.revision with full commit SHA extracted from ltsv" + task :backfill_revision => :environment do + scope = Report.where(revision: nil).where.not(ltsv: nil) + total = scope.count + puts "#{total} reports to scan" + scanned = 0 + updated = 0 + scope.find_each do |report| + scanned += 1 + sha = Report.extract_full_sha(report.ltsv) + if sha + report.update_column(:revision, sha) + updated += 1 + end + puts "#{scanned}/#{total} scanned, #{updated} updated" if scanned % 1000 == 0 + end + puts "done: #{scanned} scanned, #{updated} updated" + end +end diff --git a/test/models/report_test.rb b/test/models/report_test.rb new file mode 100644 index 000000000..15267e5d1 --- /dev/null +++ b/test/models/report_test.rb @@ -0,0 +1,98 @@ +require "test_helper" + +class ReportTest < ActiveSupport::TestCase + SHA = "0123456789abcdef0123456789abcdef01234567" + + @@ordinal = 0 + + def create_server(name) + Server.create!(name: name, uri: "https://rubyci.s3.amazonaws.com/#{name}", ordinal: (@@ordinal += 1)) + end + + def build_report(attrs = {}) + server = attrs.delete(:server) || create_server("server-#{attrs.object_id}") + Report.new({ + server: server, + branch: "master", + datetime: Time.now.utc, + summary: "ruby 3.5.0dev success", + }.merge(attrs)) + end + + test "revision accepts nil, digits and 40-hex SHA" do + assert_predicate build_report(revision: nil), :valid? + assert_predicate build_report(revision: "12345"), :valid? + assert_predicate build_report(revision: SHA), :valid? + end + + test "revision rejects other strings" do + assert_not build_report(revision: "r12345").valid? + assert_not build_report(revision: SHA[0, 11]).valid? + assert_not build_report(revision: "#{SHA} ").valid? + end + + test "revisionuri for svn revision" do + report = build_report(revision: "12345") + assert_equal "https://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=12345", report.revisionuri + end + + test "revisionuri for git commit SHA" do + report = build_report(revision: SHA) + assert_equal "https://github.com/ruby/ruby/commit/#{SHA}", report.revisionuri + end + + test "revisionuri falls back to SHA from ltsv" do + report = build_report(revision: nil, ltsv: "depsuffixed_name:ruby-master\thttps://github.com/ruby/ruby:#{SHA}") + assert_equal "https://github.com/ruby/ruby/commit/#{SHA}", report.revisionuri + end + + test "revisionuri without revision and ltsv" do + assert_nil build_report(revision: nil, ltsv: nil).revisionuri + end + + test "sha1 prefers revision column" do + report = build_report(revision: SHA, ltsv: nil) + assert_equal SHA, report.sha1 + end + + test "sha1 falls back to ltsv" do + report = build_report(revision: nil, ltsv: "https://github.com/ruby/ruby:#{SHA}") + assert_equal SHA, report.sha1 + + report = build_report(revision: nil, ltsv: %["https\\x3A//github.com/ruby/ruby":#{SHA}]) + assert_equal SHA, report.sha1 + end + + test "extract_full_sha" do + assert_equal SHA, Report.extract_full_sha("https://github.com/ruby/ruby:#{SHA}") + assert_equal SHA, Report.extract_full_sha(%["https\\x3A//github.com/ruby/ruby":#{SHA}]) + assert_nil Report.extract_full_sha("https://github.com/ruby/ruby:#{SHA[0, 11]}") + assert_nil Report.extract_full_sha(nil) + end + + test "scan_recent_ltsv stores full SHA from ltsv" do + server = create_server("scan-sha") + line = [ + "start_time:20260727T051800Z", + "https://github.com/ruby/ruby:#{SHA}", + "title:ruby 3.5.0dev (2026-07-27) [x86_64-linux]", + "result:success", + ].join("\t") + Report.scan_recent_ltsv(server, "ruby-master", line + "\n") + report = Report.where(server_id: server.id).last + assert_equal SHA, report.revision + end + + test "scan_recent_ltsv stores svn revision as digits" do + server = create_server("scan-svn") + line = [ + "start_time:20260727T051800Z", + "ruby_rev:r54321", + "title:ruby 2.7.0dev (2026-07-27) [x86_64-linux]", + "result:success", + ].join("\t") + Report.scan_recent_ltsv(server, "ruby-2.7", line + "\n") + report = Report.where(server_id: server.id).last + assert_equal "54321", report.revision + end +end