diff --git a/lib/gemstash/db/version.rb b/lib/gemstash/db/version.rb index c318aefe06..683c332e68 100644 --- a/lib/gemstash/db/version.rb +++ b/lib/gemstash/db/version.rb @@ -30,8 +30,17 @@ def self.slug(params) end end - def self.for_spec_collection(prerelease: false) - where(indexed: true, prerelease: prerelease).association_join(:rubygem) + def self.for_spec_collection(prerelease: false, latest: false) + versions = where(indexed: true, prerelease: prerelease).association_join(:rubygem) + latest ? select_latest(versions) : versions + end + + def self.select_latest(versions) + versions. + all. + group_by {|version| [version.rubygem_id, version.platform] }. + values. + map {|gem_versions| gem_versions.max_by {|version| Gem::Version.new(version.number) } } end def self.find_by_spec(gem_id, spec) diff --git a/lib/gemstash/gem_source/private_source.rb b/lib/gemstash/gem_source/private_source.rb index 0adac7d0b3..42d8c549fc 100644 --- a/lib/gemstash/gem_source/private_source.rb +++ b/lib/gemstash/gem_source/private_source.rb @@ -80,15 +80,16 @@ def serve_gem(id) end end - def serve_latest_specs - halt 403, "Not yet supported" - end - def serve_specs params[:prerelease] = false protected(Gemstash::SpecsBuilder) end + def serve_latest_specs + params[:latest] = true + protected(Gemstash::SpecsBuilder) + end + def serve_prerelease_specs params[:prerelease] = true protected(Gemstash::SpecsBuilder) diff --git a/lib/gemstash/specs_builder.rb b/lib/gemstash/specs_builder.rb index 42e4c8267a..822034e108 100644 --- a/lib/gemstash/specs_builder.rb +++ b/lib/gemstash/specs_builder.rb @@ -10,20 +10,23 @@ class SpecsBuilder attr_reader :result def self.serve(app) - prerelease = app.params[:prerelease] + prerelease = app.params.fetch(:prerelease, false) + latest = app.params.fetch(:latest, false) app.content_type "application/octet-stream" - new(app.auth, prerelease: prerelease).serve + new(app.auth, prerelease: prerelease, latest: latest).serve end def self.invalidate_stored storage = Gemstash::Storage.for("private").for("specs_collection") storage.resource("specs.4.8.gz").delete(:specs) + storage.resource("latest_specs.4.8.gz").delete(:specs) storage.resource("prerelease_specs.4.8.gz").delete(:specs) end - def initialize(auth, prerelease: false) + def initialize(auth, prerelease: false, latest: false) @auth = auth @prerelease = prerelease + @latest = latest end def serve @@ -44,7 +47,9 @@ def storage end def fetch_resource - if @prerelease + if @latest + storage.resource("latest_specs.4.8.gz") + elsif @prerelease storage.resource("prerelease_specs.4.8.gz") else storage.resource("specs.4.8.gz") @@ -61,7 +66,7 @@ def fetch_from_storage end def fetch_versions - @versions = Gemstash::DB::Version.for_spec_collection(prerelease: @prerelease).map(&:to_spec) + @versions = Gemstash::DB::Version.for_spec_collection(prerelease: @prerelease, latest: @latest).map(&:to_spec) end def marshal diff --git a/spec/gemstash/specs_builder_spec.rb b/spec/gemstash/specs_builder_spec.rb index a09ba7f13b..e1b2cb9d04 100644 --- a/spec/gemstash/specs_builder_spec.rb +++ b/spec/gemstash/specs_builder_spec.rb @@ -19,6 +19,11 @@ expect(Marshal.load(gunzip(result))).to eq([]) end + it "returns an empty latest result" do + result = Gemstash::SpecsBuilder.new(auth, latest: true).serve + expect(Marshal.load(gunzip(result))).to eq([]) + end + it "returns an empty prerelease result" do result = Gemstash::SpecsBuilder.new(auth, prerelease: true).serve expect(Marshal.load(gunzip(result))).to eq([]) @@ -33,6 +38,12 @@ ["other-example", Gem::Version.new("0.1.0"), "ruby"]] end + let(:expected_latest_specs) do + [["example", Gem::Version.new("0.0.2"), "ruby"], + ["example", Gem::Version.new("0.0.2"), "java"], + ["other-example", Gem::Version.new("0.1.0"), "ruby"]] + end + before do gem_id = insert_rubygem("example") insert_version(gem_id, "0.0.1") @@ -47,6 +58,11 @@ expect(Marshal.load(gunzip(result))).to match_array(expected_specs) end + it "marshals and gzips the latest versions" do + result = Gemstash::SpecsBuilder.new(auth, latest: true).serve + expect(Marshal.load(gunzip(result))).to match_array(expected_latest_specs) + end + it "returns an empty prerelease result" do result = Gemstash::SpecsBuilder.new(auth, prerelease: true).serve expect(Marshal.load(gunzip(result))).to eq([]) @@ -75,6 +91,11 @@ expect(Marshal.load(gunzip(result))).to eq([]) end + it "returns an empty latest result" do + result = Gemstash::SpecsBuilder.new(auth, latest: true).serve + expect(Marshal.load(gunzip(result))).to eq([]) + end + it "marshals and gzips the prerelease versions" do result = Gemstash::SpecsBuilder.new(auth, prerelease: true).serve expect(Marshal.load(gunzip(result))).to match_array(expected_prerelease_specs) @@ -89,6 +110,12 @@ ["other-example", Gem::Version.new("0.1.0"), "ruby"]] end + let(:expected_latest_specs) do + [["example", Gem::Version.new("0.0.2"), "ruby"], + ["example", Gem::Version.new("0.0.2"), "java"], + ["other-example", Gem::Version.new("0.1.0"), "ruby"]] + end + let(:expected_prerelease_specs) do [["example", Gem::Version.new("0.0.2.rc1"), "ruby"], ["example", Gem::Version.new("0.0.2.rc2"), "ruby"], @@ -114,6 +141,11 @@ expect(Marshal.load(gunzip(result))).to match_array(expected_specs) end + it "marshals and gzips the latest versions" do + result = Gemstash::SpecsBuilder.new(auth, latest: true).serve + expect(Marshal.load(gunzip(result))).to match_array(expected_latest_specs) + end + it "marshals and gzips the prerelease versions" do result = Gemstash::SpecsBuilder.new(auth, prerelease: true).serve expect(Marshal.load(gunzip(result))).to match_array(expected_prerelease_specs) @@ -128,6 +160,12 @@ ["other-example", Gem::Version.new("0.1.0"), "ruby"]] end + let(:expected_latest_specs) do + [["example", Gem::Version.new("0.0.2"), "ruby"], + ["example", Gem::Version.new("0.0.2"), "java"], + ["other-example", Gem::Version.new("0.1.0"), "ruby"]] + end + let(:expected_prerelease_specs) do [["example", Gem::Version.new("0.0.2.rc1"), "ruby"], ["example", Gem::Version.new("0.0.2.rc2"), "ruby"], @@ -159,6 +197,11 @@ expect(Marshal.load(gunzip(result))).to match_array(expected_specs) end + it "marshals and gzips the latest versions" do + result = Gemstash::SpecsBuilder.new(auth, latest: true).serve + expect(Marshal.load(gunzip(result))).to match_array(expected_latest_specs) + end + it "marshals and gzips the prerelease versions" do result = Gemstash::SpecsBuilder.new(auth, prerelease: true).serve expect(Marshal.load(gunzip(result))).to match_array(expected_prerelease_specs) @@ -167,7 +210,8 @@ context "with a new spec pushed" do let(:initial_specs) { [["example", Gem::Version.new("0.0.1"), "ruby"]] } - let(:specs_after_push) { initial_specs + [["example", Gem::Version.new("0.1.0"), "ruby"]] } + let(:new_specs) { [["example", Gem::Version.new("0.1.0"), "ruby"]] } + let(:specs_after_push) { initial_specs + new_specs } before do Gemstash::Authorization.authorize(auth_key, "all") @@ -182,6 +226,14 @@ result = Gemstash::SpecsBuilder.new(auth).serve expect(Marshal.load(gunzip(result))).to match_array(specs_after_push) end + + it "busts the latest cache" do + result = Gemstash::SpecsBuilder.new(auth, latest: true).serve + expect(Marshal.load(gunzip(result))).to match_array(initial_specs) + Gemstash::GemPusher.new(auth, read_gem("example", "0.1.0")).serve + result = Gemstash::SpecsBuilder.new(auth, latest: true).serve + expect(Marshal.load(gunzip(result))).to match_array(new_specs) + end end context "with a new prerelease spec pushed" do @@ -209,6 +261,8 @@ ["example", Gem::Version.new("0.1.0"), "ruby"]] end + let(:latest_specs) { [["example", Gem::Version.new("0.1.0"), "ruby"]] } + let(:specs_after_yank) { [["example", Gem::Version.new("0.0.1"), "ruby"]] } before do @@ -225,6 +279,14 @@ result = Gemstash::SpecsBuilder.new(auth).serve expect(Marshal.load(gunzip(result))).to match_array(specs_after_yank) end + + it "busts the latest cache" do + result = Gemstash::SpecsBuilder.new(auth, latest: true).serve + expect(Marshal.load(gunzip(result))).to match_array(latest_specs) + Gemstash::GemYanker.new(auth, "example", "0.1.0").serve + result = Gemstash::SpecsBuilder.new(auth, latest: true).serve + expect(Marshal.load(gunzip(result))).to match_array(specs_after_yank) + end end context "with a prerelease spec yanked" do @@ -253,7 +315,8 @@ context "with a spec unyanked" do let(:initial_specs) { [["example", Gem::Version.new("0.0.1"), "ruby"]] } - let(:specs_after_unyank) { initial_specs + [["example", Gem::Version.new("0.1.0"), "ruby"]] } + let(:new_specs) { [["example", Gem::Version.new("0.1.0"), "ruby"]] } + let(:specs_after_unyank) { initial_specs + new_specs } before do Gemstash::Authorization.authorize(auth_key, "all") @@ -270,6 +333,14 @@ result = Gemstash::SpecsBuilder.new(auth).serve expect(Marshal.load(gunzip(result))).to match_array(specs_after_unyank) end + + it "busts the latest cache" do + result = Gemstash::SpecsBuilder.new(auth, latest: true).serve + expect(Marshal.load(gunzip(result))).to match_array(initial_specs) + Gemstash::GemUnyanker.new(auth, "example", "0.1.0").serve + result = Gemstash::SpecsBuilder.new(auth, latest: true).serve + expect(Marshal.load(gunzip(result))).to match_array(new_specs) + end end context "with a prerelease spec unyanked" do diff --git a/spec/integration_spec.rb b/spec/integration_spec.rb index 962e639c62..62aef0a3ad 100644 --- a/spec/integration_spec.rb +++ b/spec/integration_spec.rb @@ -131,6 +131,12 @@ to exit_success.and_output(/speaker \(0.1.0\)/) end + it "finds the latest version of private gems", db_transaction: false do + env = { "HOME" => env_dir } + expect(execute("gem", ["search", "-r", "speaker", "--clear-sources", "--source", host], env: env)). + to exit_success.and_output(/speaker \(0.1.0\)/) + end + it "finds private gems when just the private source is configured", db_transaction: false do skip "this doesn't work because Rubygems sends /specs.4.8.gz instead of /private/specs.4.8.gz" env = { "HOME" => env_dir }