From 2bfa35bdf2841e00eb3d418c31a0fe850bedb16a Mon Sep 17 00:00:00 2001 From: Grey Baker Date: Tue, 9 Jan 2018 17:03:33 +0000 Subject: [PATCH 01/28] Add starter pack for Go --- lib/dependabot/file_fetchers.rb | 2 + lib/dependabot/file_fetchers/go/dep.rb | 36 +++++++++++ lib/dependabot/file_parsers.rb | 2 + lib/dependabot/file_parsers/go/dep.rb | 42 +++++++++++++ lib/dependabot/file_updaters.rb | 2 + lib/dependabot/file_updaters/go/dep.rb | 60 +++++++++++++++++++ lib/dependabot/metadata_finders.rb | 2 + lib/dependabot/metadata_finders/go/dep.rb | 19 ++++++ lib/dependabot/update_checkers.rb | 2 + lib/dependabot/update_checkers/go/dep.rb | 39 ++++++++++++ lib/dependabot/utils.rb | 5 +- spec/dependabot/file_fetchers/go/dep_spec.rb | 8 +++ spec/dependabot/file_parsers/go/dep_spec.rb | 8 +++ spec/dependabot/file_updaters/go/dep_spec.rb | 8 +++ .../metadata_finders/go/dep_spec.rb | 8 +++ .../dependabot/update_checkers/go/dep_spec.rb | 8 +++ 16 files changed, 249 insertions(+), 2 deletions(-) create mode 100644 lib/dependabot/file_fetchers/go/dep.rb create mode 100644 lib/dependabot/file_parsers/go/dep.rb create mode 100644 lib/dependabot/file_updaters/go/dep.rb create mode 100644 lib/dependabot/metadata_finders/go/dep.rb create mode 100644 lib/dependabot/update_checkers/go/dep.rb create mode 100644 spec/dependabot/file_fetchers/go/dep_spec.rb create mode 100644 spec/dependabot/file_parsers/go/dep_spec.rb create mode 100644 spec/dependabot/file_updaters/go/dep_spec.rb create mode 100644 spec/dependabot/metadata_finders/go/dep_spec.rb create mode 100644 spec/dependabot/update_checkers/go/dep_spec.rb diff --git a/lib/dependabot/file_fetchers.rb b/lib/dependabot/file_fetchers.rb index 652887558f4..26fcc1bc820 100644 --- a/lib/dependabot/file_fetchers.rb +++ b/lib/dependabot/file_fetchers.rb @@ -11,6 +11,7 @@ require "dependabot/file_fetchers/elixir/hex" require "dependabot/file_fetchers/rust/cargo" require "dependabot/file_fetchers/dotnet/nuget" +require "dependabot/file_fetchers/go/dep" module Dependabot module FileFetchers @@ -28,6 +29,7 @@ def self.for_package_manager(package_manager) when "hex" then FileFetchers::Elixir::Hex when "cargo" then FileFetchers::Rust::Cargo when "nuget" then FileFetchers::Dotnet::Nuget + when "dep" then FileFetchers::Go::Dep else raise "Unsupported package_manager #{package_manager}" end end diff --git a/lib/dependabot/file_fetchers/go/dep.rb b/lib/dependabot/file_fetchers/go/dep.rb new file mode 100644 index 00000000000..554a11e57e5 --- /dev/null +++ b/lib/dependabot/file_fetchers/go/dep.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require "dependabot/file_fetchers/base" + +module Dependabot + module FileFetchers + module Go + class Dep < Dependabot::FileFetchers::Base + def self.required_files_in?(filenames) + (%w(Gopkg.toml Gopkg.lock) - filenames).empty? + end + + def self.required_files_message + "Repo must contain a Gopkg.toml and Gopkg.lock." + end + + private + + def fetch_files + fetched_files = [] + fetched_files << manifest + fetched_files << lockfile + fetched_files + end + + def manifest + @manifest ||= fetch_file_from_github("Gopkg.toml") + end + + def lockfile + @lockfile ||= fetch_file_from_github("Gopkg.lock") + end + end + end + end +end diff --git a/lib/dependabot/file_parsers.rb b/lib/dependabot/file_parsers.rb index 01361295edc..ef74d900f53 100644 --- a/lib/dependabot/file_parsers.rb +++ b/lib/dependabot/file_parsers.rb @@ -11,6 +11,7 @@ require "dependabot/file_parsers/elixir/hex" require "dependabot/file_parsers/rust/cargo" require "dependabot/file_parsers/dotnet/nuget" +require "dependabot/file_parsers/go/dep" module Dependabot module FileParsers @@ -28,6 +29,7 @@ def self.for_package_manager(package_manager) when "hex" then FileParsers::Elixir::Hex when "cargo" then FileParsers::Rust::Cargo when "nuget" then FileParsers::Dotnet::Nuget + when "dep" then FileParsers::Go::Dep else raise "Unsupported package_manager #{package_manager}" end end diff --git a/lib/dependabot/file_parsers/go/dep.rb b/lib/dependabot/file_parsers/go/dep.rb new file mode 100644 index 00000000000..3f0f53df874 --- /dev/null +++ b/lib/dependabot/file_parsers/go/dep.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require "dependabot/dependency" +require "dependabot/file_parsers/base" + +module Dependabot + module FileParsers + module Go + class Dep < Dependabot::FileParsers::Base + def parse + # Parse the dependency file and return a Dependabot::Dependency + # object for each dependency. + # If possible, this should be done in Ruby (since it's easier to + # maintain). However, if we need to parse a lockfile that has a + # non-standard format we can shell out to a helper in a language of + # our choice (see JavaScript example where we parse the yarn.lock). + [ + Dependency.new( + name: "my_dependency", + version: "1.0.1", + package_manager: "dep", + requirements: [{ + requirement: ">= 1.0.0", + file: "Gopkg.toml", + groups: [], + source: nil + }] + ) + ] + end + + private + + def check_required_files + %w(Gopkg.toml Gopkg.lock).each do |filename| + raise "No #{filename}!" unless get_original_file(filename) + end + end + end + end + end +end diff --git a/lib/dependabot/file_updaters.rb b/lib/dependabot/file_updaters.rb index f4376b4dfd1..57a78780981 100644 --- a/lib/dependabot/file_updaters.rb +++ b/lib/dependabot/file_updaters.rb @@ -11,6 +11,7 @@ require "dependabot/file_updaters/elixir/hex" require "dependabot/file_updaters/rust/cargo" require "dependabot/file_updaters/dotnet/nuget" +require "dependabot/file_updaters/go/dep" module Dependabot module FileUpdaters @@ -28,6 +29,7 @@ def self.for_package_manager(package_manager) when "hex" then FileUpdaters::Elixir::Hex when "cargo" then FileUpdaters::Rust::Cargo when "nuget" then FileUpdaters::Dotnet::Nuget + when "dep" then FileUpdaters::Go::Dep else raise "Unsupported package_manager #{package_manager}" end end diff --git a/lib/dependabot/file_updaters/go/dep.rb b/lib/dependabot/file_updaters/go/dep.rb new file mode 100644 index 00000000000..b2b08cafc98 --- /dev/null +++ b/lib/dependabot/file_updaters/go/dep.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require "dependabot/file_updaters/base" + +module Dependabot + module FileUpdaters + module Go + class Dep < Dependabot::FileUpdaters::Base + def self.updated_files_regex + [ + /^Gopkg\.toml$/, + /^Gopkg\.lock$/ + ] + end + + def updated_dependency_files + updated_files = [] + + if file_changed?(manifest) + updated_files << + updated_file( + file: manifest, + content: updated_manifest_content + ) + end + + updated_files << + updated_file(file: lockfile, content: updated_lockfile_content) + + updated_files + end + + private + + def check_required_files + %w(Gopkg.toml Gopkg.lock).each do |filename| + raise "No #{filename}!" unless get_original_file(filename) + end + end + + def manifest + @manifest ||= get_original_file("Gopkg.toml") + end + + def lockfile + @lockfile ||= get_original_file("Gopkg.lock") + end + + def updated_manifest_content + # TODO: This can normally be written using regexs + end + + def updated_lockfile_content + # TODO: This normally needs to be written in the native language. + # We do so by shelling out to a helper method (see other languages) + end + end + end + end +end diff --git a/lib/dependabot/metadata_finders.rb b/lib/dependabot/metadata_finders.rb index c55e9775deb..3fa4eebc6f8 100644 --- a/lib/dependabot/metadata_finders.rb +++ b/lib/dependabot/metadata_finders.rb @@ -10,6 +10,7 @@ require "dependabot/metadata_finders/elixir/hex" require "dependabot/metadata_finders/rust/cargo" require "dependabot/metadata_finders/dotnet/nuget" +require "dependabot/metadata_finders/go/dep" module Dependabot module MetadataFinders @@ -26,6 +27,7 @@ def self.for_package_manager(package_manager) when "hex" then MetadataFinders::Elixir::Hex when "cargo" then MetadataFinders::Rust::Cargo when "nuget" then MetadataFinders::Dotnet::Nuget + when "dep" then MetadataFinders::Go::Dep else raise "Unsupported package_manager #{package_manager}" end end diff --git a/lib/dependabot/metadata_finders/go/dep.rb b/lib/dependabot/metadata_finders/go/dep.rb new file mode 100644 index 00000000000..b95709af550 --- /dev/null +++ b/lib/dependabot/metadata_finders/go/dep.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require "dependabot/metadata_finders/base" + +module Dependabot + module MetadataFinders + module Go + class Dep < Dependabot::MetadataFinders::Base + private + + def look_up_source + # Hit the registry (or some other source) and get details of the + # location of the source code for the given dependency + Source.new(host: "github.com", repo: "my-org/my-dependency") + end + end + end + end +end diff --git a/lib/dependabot/update_checkers.rb b/lib/dependabot/update_checkers.rb index d02adb264ce..903501cdc23 100644 --- a/lib/dependabot/update_checkers.rb +++ b/lib/dependabot/update_checkers.rb @@ -11,6 +11,7 @@ require "dependabot/update_checkers/elixir/hex" require "dependabot/update_checkers/rust/cargo" require "dependabot/update_checkers/dotnet/nuget" +require "dependabot/update_checkers/go/dep" module Dependabot module UpdateCheckers @@ -28,6 +29,7 @@ def self.for_package_manager(package_manager) when "hex" then UpdateCheckers::Elixir::Hex when "cargo" then UpdateCheckers::Rust::Cargo when "nuget" then UpdateCheckers::Dotnet::Nuget + when "dep" then UpdateCheckers::Go::Dep else raise "Unsupported package_manager #{package_manager}" end end diff --git a/lib/dependabot/update_checkers/go/dep.rb b/lib/dependabot/update_checkers/go/dep.rb new file mode 100644 index 00000000000..f4ef1df558b --- /dev/null +++ b/lib/dependabot/update_checkers/go/dep.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require "dependabot/update_checkers/base" + +module Dependabot + module UpdateCheckers + module Go + class Dep < Dependabot::UpdateCheckers::Base + def latest_version + # Hit the registry for this dependency and get its latest version + end + + def latest_resolvable_version + # Resolving the dependency files to get the latest version of + # this dependency that doesn't cause conflicts is hard, and needs to + # be done through a language helper that piggy-backs off of the + # package manager's own resolution logic (see PHP, for example). + end + + def updated_requirements + # If the dependency file needs to be updated we store the updated + # requirements on the dependency. + dependency.requirements + end + + private + + def latest_version_resolvable_with_full_unlock? + # Full unlock checks aren't implemented for Go (yet) + false + end + + def updated_dependencies_after_full_unlock + raise NotImplementedError + end + end + end + end +end diff --git a/lib/dependabot/utils.rb b/lib/dependabot/utils.rb index 216023bee47..b737901aba5 100644 --- a/lib/dependabot/utils.rb +++ b/lib/dependabot/utils.rb @@ -22,7 +22,7 @@ module Dependabot module Utils def self.version_class_for_package_manager(package_manager) case package_manager - when "bundler", "submodules", "docker" then Gem::Version + when "bundler", "submodules", "docker", "dep" then Gem::Version when "nuget" then Utils::Dotnet::Version when "maven" then Utils::Java::Version when "gradle" then Utils::Java::Version @@ -37,7 +37,8 @@ def self.version_class_for_package_manager(package_manager) def self.requirement_class_for_package_manager(package_manager) case package_manager - when "bundler", "submodules", "docker" then Utils::Ruby::Requirement + when "bundler", "submodules", "docker", "dep" + Utils::Ruby::Requirement when "nuget" then Utils::Dotnet::Requirement when "maven" then Utils::Java::Requirement when "gradle" then Utils::Java::Requirement diff --git a/spec/dependabot/file_fetchers/go/dep_spec.rb b/spec/dependabot/file_fetchers/go/dep_spec.rb new file mode 100644 index 00000000000..b54b2685f9b --- /dev/null +++ b/spec/dependabot/file_fetchers/go/dep_spec.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require "dependabot/file_fetchers/go/dep" +require_relative "../shared_examples_for_file_fetchers" + +RSpec.describe Dependabot::FileFetchers::Go::Dep do + it_behaves_like "a dependency file fetcher" +end diff --git a/spec/dependabot/file_parsers/go/dep_spec.rb b/spec/dependabot/file_parsers/go/dep_spec.rb new file mode 100644 index 00000000000..2f6587271e3 --- /dev/null +++ b/spec/dependabot/file_parsers/go/dep_spec.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require "dependabot/file_parsers/go/dep" +require_relative "../shared_examples_for_file_parsers" + +RSpec.describe Dependabot::FileParsers::Go::Dep do + it_behaves_like "a dependency file parser" +end diff --git a/spec/dependabot/file_updaters/go/dep_spec.rb b/spec/dependabot/file_updaters/go/dep_spec.rb new file mode 100644 index 00000000000..2cbbdeb595a --- /dev/null +++ b/spec/dependabot/file_updaters/go/dep_spec.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require "dependabot/file_updaters/go/dep" +require_relative "../shared_examples_for_file_updaters" + +RSpec.describe Dependabot::FileUpdaters::Go::Dep do + it_behaves_like "a dependency file updater" +end diff --git a/spec/dependabot/metadata_finders/go/dep_spec.rb b/spec/dependabot/metadata_finders/go/dep_spec.rb new file mode 100644 index 00000000000..e047780b381 --- /dev/null +++ b/spec/dependabot/metadata_finders/go/dep_spec.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require "dependabot/metadata_finders/go/dep" +require_relative "../shared_examples_for_metadata_finders" + +RSpec.describe Dependabot::MetadataFinders::Go::Dep do + it_behaves_like "a dependency metadata finder" +end diff --git a/spec/dependabot/update_checkers/go/dep_spec.rb b/spec/dependabot/update_checkers/go/dep_spec.rb new file mode 100644 index 00000000000..86ad634fb16 --- /dev/null +++ b/spec/dependabot/update_checkers/go/dep_spec.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require "dependabot/update_checkers/go/dep" +require_relative "../shared_examples_for_update_checkers" + +RSpec.describe Dependabot::UpdateCheckers::Go::Dep do + it_behaves_like "an update checker" +end From 132411c7ebb9b39503180c6d91b535c68a0f6923 Mon Sep 17 00:00:00 2001 From: Grey Baker Date: Wed, 18 Jul 2018 21:12:03 -0400 Subject: [PATCH 02/28] Dep: Make fetcher work --- lib/dependabot/file_fetchers/go/dep.rb | 4 +- spec/dependabot/file_fetchers/go/dep_spec.rb | 71 +++++++++++++++++++ spec/fixtures/github/contents_gopkg_lock.json | 18 +++++ spec/fixtures/github/contents_gopkg_toml.json | 18 +++++ 4 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 spec/fixtures/github/contents_gopkg_lock.json create mode 100644 spec/fixtures/github/contents_gopkg_toml.json diff --git a/lib/dependabot/file_fetchers/go/dep.rb b/lib/dependabot/file_fetchers/go/dep.rb index 554a11e57e5..732efa194bc 100644 --- a/lib/dependabot/file_fetchers/go/dep.rb +++ b/lib/dependabot/file_fetchers/go/dep.rb @@ -24,11 +24,11 @@ def fetch_files end def manifest - @manifest ||= fetch_file_from_github("Gopkg.toml") + @manifest ||= fetch_file_from_host("Gopkg.toml") end def lockfile - @lockfile ||= fetch_file_from_github("Gopkg.lock") + @lockfile ||= fetch_file_from_host("Gopkg.lock") end end end diff --git a/spec/dependabot/file_fetchers/go/dep_spec.rb b/spec/dependabot/file_fetchers/go/dep_spec.rb index b54b2685f9b..b14ef751644 100644 --- a/spec/dependabot/file_fetchers/go/dep_spec.rb +++ b/spec/dependabot/file_fetchers/go/dep_spec.rb @@ -5,4 +5,75 @@ RSpec.describe Dependabot::FileFetchers::Go::Dep do it_behaves_like "a dependency file fetcher" + + let(:source) do + Dependabot::Source.new( + provider: "github", + repo: "gocardless/bump", + directory: directory + ) + end + let(:file_fetcher_instance) do + described_class.new(source: source, credentials: credentials) + end + let(:directory) { "/" } + let(:url) { "https://api.github.com/repos/gocardless/bump/contents/" } + let(:credentials) do + [{ + "type" => "git_source", + "host" => "github.com", + "username" => "x-access-token", + "password" => "token" + }] + end + + before do + allow(file_fetcher_instance).to receive(:commit).and_return("sha") + + stub_request(:get, url + "Gopkg.toml?ref=sha"). + with(headers: { "Authorization" => "token token" }). + to_return( + status: 200, + body: fixture("github", "contents_gopkg_toml.json"), + headers: { "content-type" => "application/json" } + ) + stub_request(:get, url + "Gopkg.lock?ref=sha"). + with(headers: { "Authorization" => "token token" }). + to_return( + status: 200, + body: fixture("github", "contents_gopkg_lock.json"), + headers: { "content-type" => "application/json" } + ) + end + + it "fetches the Gopkg.toml and Gopkg.lock" do + expect(file_fetcher_instance.files.map(&:name)). + to match_array(%w(Gopkg.toml Gopkg.lock)) + end + + context "without a Gopkg.lock" do + before do + stub_request(:get, url + "Gopkg.lock?ref=sha"). + with(headers: { "Authorization" => "token token" }). + to_return(status: 404) + end + + it "raises a helpful error" do + expect { file_fetcher_instance.files }. + to raise_error(Dependabot::DependencyFileNotFound) + end + end + + context "without a Gopkg.toml" do + before do + stub_request(:get, url + "Gopkg.toml?ref=sha"). + with(headers: { "Authorization" => "token token" }). + to_return(status: 404) + end + + it "raises a helpful error" do + expect { file_fetcher_instance.files }. + to raise_error(Dependabot::DependencyFileNotFound) + end + end end diff --git a/spec/fixtures/github/contents_gopkg_lock.json b/spec/fixtures/github/contents_gopkg_lock.json new file mode 100644 index 00000000000..07b370ce448 --- /dev/null +++ b/spec/fixtures/github/contents_gopkg_lock.json @@ -0,0 +1,18 @@ +{ + "name": "Gopkg.lock", + "path": "Gopkg.lock", + "sha": "816ac07074ab08023f4fea593888b1f87dd774b3", + "size": 29707, + "url": "https://api.github.com/repos/cockroachdb/cockroach/contents/Gopkg.lock?ref=master", + "html_url": "https://github.com/cockroachdb/cockroach/blob/master/Gopkg.lock", + "git_url": "https://api.github.com/repos/cockroachdb/cockroach/git/blobs/816ac07074ab08023f4fea593888b1f87dd774b3", + "download_url": "https://raw.githubusercontent.com/cockroachdb/cockroach/master/Gopkg.lock", + "type": "file", + "content": "IyBUaGlzIGZpbGUgaXMgYXV0b2dlbmVyYXRlZCwgZG8gbm90IGVkaXQ7IGNo\nYW5nZXMgbWF5IGJlIHVuZG9uZSBieSB0aGUgbmV4dCAnZGVwIGVuc3VyZScu\nCgoKW1twcm9qZWN0c11dCiAgbmFtZSA9ICJjbG91ZC5nb29nbGUuY29tL2dv\nIgogIHBhY2thZ2VzID0gWwogICAgImNvbXB1dGUvbWV0YWRhdGEiLAogICAg\nImlhbSIsCiAgICAiaW50ZXJuYWwiLAogICAgImludGVybmFsL29wdGlvbmFs\nIiwKICAgICJpbnRlcm5hbC90cmFjZSIsCiAgICAiaW50ZXJuYWwvdmVyc2lv\nbiIsCiAgICAic3RvcmFnZSIsCiAgXQogIHJldmlzaW9uID0gIjBmZDcyMzBi\nMmE3NTA1ODMzZDVmNjliNzVjYmQ2Yzk1ODI0MDE0NzkiCiAgdmVyc2lvbiA9\nICJ2MC4yMy4wIgoKW1twcm9qZWN0c11dCiAgbmFtZSA9ICJnaXRodWIuY29t\nL0F6dXJlL2F6dXJlLXNkay1mb3ItZ28iCiAgcGFja2FnZXMgPSBbCiAgICAi\nc3RvcmFnZSIsCiAgICAidmVyc2lvbiIsCiAgXQogIHJldmlzaW9uID0gImU2\nN2NkMzllOTQyYzQxN2FlNWU5YWUxMTY1Zjc3OGQ5ZmU4OTk2ZTAiCiAgdmVy\nc2lvbiA9ICJ2MTQuNS4wIgoKW1twcm9qZWN0c11dCiAgYnJhbmNoID0gIm1h\nc3RlciIKICBuYW1lID0gImdpdGh1Yi5jb20vQXp1cmUvZ28tYW5zaXRlcm0i\nCiAgcGFja2FnZXMgPSBbCiAgICAiLiIsCiAgICAid2ludGVybSIsCiAgXQog\nIHJldmlzaW9uID0gImQ2ZTNiMzMyOGI3ODNmMjM3MzFiYzRkMDU4ODc1YjAz\nNzFmZjgxMDkiCgpbW3Byb2plY3RzXV0KICBuYW1lID0gImdpdGh1Yi5jb20v\nQXp1cmUvZ28tYXV0b3Jlc3QiCiAgcGFja2FnZXMgPSBbCiAgICAiYXV0b3Jl\nc3QiLAogICAgImF1dG9yZXN0L2FkYWwiLAogICAgImF1dG9yZXN0L2F6dXJl\nIiwKICAgICJhdXRvcmVzdC9kYXRlIiwKICBdCiAgcmV2aXNpb24gPSAiMGFl\nMzZhOWU1NDQ2OTZkZTQ2ZmRhZGI3YjBkNWZiMzhhZjQ4YzA2MyIKICB2ZXJz\naW9uID0gInYxMC4yLjAiCgpbW3Byb2plY3RzXV0KICBicmFuY2ggPSAicGFy\nc2UtY29uc3RyYWludHMtd2l0aC1kYXNoLWluLXByZSIKICBuYW1lID0gImdp\ndGh1Yi5jb20vTWFzdGVybWluZHMvc2VtdmVyIgogIHBhY2thZ2VzID0gWyIu\nIl0KICByZXZpc2lvbiA9ICJhOTNlNTFiNWE1N2VmNDE2ZGFjOGJiMDJkMTE0\nMDdiNmY1NWQ4OTI5IgogIHNvdXJjZSA9ICJodHRwczovL2dpdGh1Yi5jb20v\nY2Fyb2x5bnZzL3NlbXZlci5naXQiCgpbW3Byb2plY3RzXV0KICBuYW1lID0g\nImdpdGh1Yi5jb20vTWFzdGVybWluZHMvdmNzIgogIHBhY2thZ2VzID0gWyIu\nIl0KICByZXZpc2lvbiA9ICI2ZjFjNmQxNTA1MDBlNDUyNzA0ZTk4NjNmNjhj\nMjU1OWY1ODYxNmJmIgogIHZlcnNpb24gPSAidjEuMTIuMCIKCltbcHJvamVj\ndHNdXQogIGJyYW5jaCA9ICJtYXN0ZXIiCiAgbmFtZSA9ICJnaXRodWIuY29t\nL01pY2hhZWxUSm9uZXMvd2FsayIKICBwYWNrYWdlcyA9IFsiLiJdCiAgcmV2\naXNpb24gPSAiNDc0OGUyOWQ1NzE4YzJkZjQwMjhhNjU0M2VkZjg2ZmQ4Y2Mw\nZjg4MSIKCltbcHJvamVjdHNdXQogIG5hbWUgPSAiZ2l0aHViLmNvbS9NaWNy\nb3NvZnQvZ28td2luaW8iCiAgcGFja2FnZXMgPSBbIi4iXQogIHJldmlzaW9u\nID0gIjc4NDM5OTY2YjM4ZDY5YmYzODIyN2ZiZjU3YWM4YTZmZWU3MGY2OWEi\nCiAgdmVyc2lvbiA9ICJ2MC40LjUiCgpbW3Byb2plY3RzXV0KICBicmFuY2gg\nPSAibWFzdGVyIgogIG5hbWUgPSAiZ2l0aHViLmNvbS9OdnZlZW4vR290dHki\nCiAgcGFja2FnZXMgPSBbIi4iXQogIHJldmlzaW9uID0gImNkNTI3Mzc0ZjFl\nNWJmZjQ5MzgyMDc2MDRhMTRmMmUzOGE5Y2Y1MTIiCgpbW3Byb2plY3RzXV0K\nICBuYW1lID0gImdpdGh1Yi5jb20vUHVlcmtpdG9CaW8vZ29xdWVyeSIKICBw\nYWNrYWdlcyA9IFsiLiJdCiAgcmV2aXNpb24gPSAiZTEyNzFlZTM0YzZhMzA1\nZTM4NTY2ZWNkMjdhZTM3NDk0NDkwN2VlOSIKICB2ZXJzaW9uID0gInYxLjEu\nMCIKCltbcHJvamVjdHNdXQogIG5hbWUgPSAiZ2l0aHViLmNvbS9TaG9waWZ5\nL3NhcmFtYSIKICBwYWNrYWdlcyA9IFsiLiJdCiAgcmV2aXNpb24gPSAiYmJk\nYmU2NDQwOTliN2ZkYzgzMjdkNWNjNjljMDMwOTQ1MTg4YjJlOSIKICB2ZXJz\naW9uID0gInYxLjEzLjAiCgpbW3Byb2plY3RzXV0KICBicmFuY2ggPSAibWFz\ndGVyIgogIG5hbWUgPSAiZ2l0aHViLmNvbS9TdGFja0V4Y2hhbmdlL3dtaSIK\nICBwYWNrYWdlcyA9IFsiLiJdCiAgcmV2aXNpb24gPSAiZWEzODNjZjNiYTZl\nYzk1MDg3NGI4NDg2Y2Q3MjM1NmQwMDdjNzY4ZiIKCltbcHJvamVjdHNdXQog\nIG5hbWUgPSAiZ2l0aHViLmNvbS9WaXZpZENvcnRleC9ld21hIgogIHBhY2th\nZ2VzID0gWyIuIl0KICByZXZpc2lvbiA9ICJiMjRlYjM0NmE5NGMzYmExMmMx\nZGExZTU2NGRiYWMxYjQ5OGE3N2NlIgogIHZlcnNpb24gPSAidjEuMS4xIgoK\nW1twcm9qZWN0c11dCiAgYnJhbmNoID0gIm1hc3RlciIKICBuYW1lID0gImdp\ndGh1Yi5jb20vYWJvdXJnZXQvdGVhbWNpdHkiCiAgcGFja2FnZXMgPSBbIi4i\nXQogIHJldmlzaW9uID0gImUyNDExMDQzOTRmOTFiZjRlNjUwNDhmOWVhNWQw\nYzBmM2MyNWIzNWUiCgpbW3Byb2plY3RzXV0KICBicmFuY2ggPSAibWFzdGVy\nIgogIG5hbWUgPSAiZ2l0aHViLmNvbS9hbmR5LWtpbWJhbGwvYXJlbmFza2wi\nCiAgcGFja2FnZXMgPSBbIi4iXQogIHJldmlzaW9uID0gIjIyNDc2MWU1NTJh\nZmU2NGRiOWQ5MzAwNGY4ZDVkM2E2ODZiODk3NzEiCgpbW3Byb2plY3RzXV0K\nICBicmFuY2ggPSAibWFzdGVyIgogIG5hbWUgPSAiZ2l0aHViLmNvbS9hbmR5\nYmFsaG9sbS9jYXNjYWRpYSIKICBwYWNrYWdlcyA9IFsiLiJdCiAgcmV2aXNp\nb24gPSAiMzQ5ZGQwMjA5NDcwZWFiZDk1MTQyNDJjNjg4YzQwM2MwOTI2ZDI2\nNiIKCltbcHJvamVjdHNdXQogIG5hbWUgPSAiZ2l0aHViLmNvbS9hcGFjaGUv\ndGhyaWZ0IgogIHBhY2thZ2VzID0gWyJsaWIvZ28vdGhyaWZ0Il0KICByZXZp\nc2lvbiA9ICIzMjdlYmI2YzJiNmRmOGJmMDc1ZGEwMmVmNDVhMmEwMzRlOWI3\nOWJhIgogIHZlcnNpb24gPSAiMC4xMS4wIgoKW1twcm9qZWN0c11dCiAgYnJh\nbmNoID0gIm1hc3RlciIKICBuYW1lID0gImdpdGh1Yi5jb20vYXJtb24vZ28t\ncmFkaXgiCiAgcGFja2FnZXMgPSBbIi4iXQogIHJldmlzaW9uID0gIjFmY2Ex\nNDVkZmZiY2FhOGZlOTE0MzA5YjFlYzBjZmM2NzUwMGZlNjEiCgpbW3Byb2pl\nY3RzXV0KICBuYW1lID0gImdpdGh1Yi5jb20vYXdzL2F3cy1zZGstZ28iCiAg\ncGFja2FnZXMgPSBbCiAgICAiYXdzIiwKICAgICJhd3MvYXdzZXJyIiwKICAg\nICJhd3MvYXdzdXRpbCIsCiAgICAiYXdzL2NsaWVudCIsCiAgICAiYXdzL2Ns\naWVudC9tZXRhZGF0YSIsCiAgICAiYXdzL2NvcmVoYW5kbGVycyIsCiAgICAi\nYXdzL2NyZWRlbnRpYWxzIiwKICAgICJhd3MvY3JlZGVudGlhbHMvZWMycm9s\nZWNyZWRzIiwKICAgICJhd3MvY3JlZGVudGlhbHMvZW5kcG9pbnRjcmVkcyIs\nCiAgICAiYXdzL2NyZWRlbnRpYWxzL3N0c2NyZWRzIiwKICAgICJhd3MvZGVm\nYXVsdHMiLAogICAgImF3cy9lYzJtZXRhZGF0YSIsCiAgICAiYXdzL2VuZHBv\naW50cyIsCiAgICAiYXdzL3JlcXVlc3QiLAogICAgImF3cy9zZXNzaW9uIiwK\nICAgICJhd3Mvc2lnbmVyL3Y0IiwKICAgICJpbnRlcm5hbC9zaGFyZWRkZWZh\ndWx0cyIsCiAgICAicHJpdmF0ZS9wcm90b2NvbCIsCiAgICAicHJpdmF0ZS9w\ncm90b2NvbC9xdWVyeSIsCiAgICAicHJpdmF0ZS9wcm90b2NvbC9xdWVyeS9x\ndWVyeXV0aWwiLAogICAgInByaXZhdGUvcHJvdG9jb2wvcmVzdCIsCiAgICAi\ncHJpdmF0ZS9wcm90b2NvbC9yZXN0eG1sIiwKICAgICJwcml2YXRlL3Byb3Rv\nY29sL3htbC94bWx1dGlsIiwKICAgICJzZXJ2aWNlL3MzIiwKICAgICJzZXJ2\naWNlL3MzL3MzaWZhY2UiLAogICAgInNlcnZpY2UvczMvczNtYW5hZ2VyIiwK\nICAgICJzZXJ2aWNlL3N0cyIsCiAgXQogIHJldmlzaW9uID0gImVlMWYxNzk4\nNzdiMmRhZjJhYWFiZjcxZmE5MDA3NzNiZjg4NDIyNTMiCiAgdmVyc2lvbiA9\nICJ2MS4xMi4xOSIKCltbcHJvamVjdHNdXQogIGJyYW5jaCA9ICJtYXN0ZXIi\nCiAgbmFtZSA9ICJnaXRodWIuY29tL2F4aW9taHEvaHlwZXJsb2dsb2ciCiAg\ncGFja2FnZXMgPSBbIi4iXQogIHJldmlzaW9uID0gImJhYmE4MDBiZTA5OGQ5\nZjQzMDMzNTJmYTYyMTRhOTMzZTM3MWUzZGEiCgpbW3Byb2plY3RzXV0KICBi\ncmFuY2ggPSAibWFzdGVyIgogIG5hbWUgPSAiZ2l0aHViLmNvbS9iYWNrdHJh\nY2UtbGFicy9nby1iY2QiCiAgcGFja2FnZXMgPSBbIi4iXQogIHJldmlzaW9u\nID0gIjVkOGUwMWIyZjA0Mzg5MjIyODkyMzhmZDNiYTA0Mzc2MWRhZjExMDIi\nCgpbW3Byb2plY3RzXV0KICBicmFuY2ggPSAibWFzdGVyIgogIG5hbWUgPSAi\nZ2l0aHViLmNvbS9iZW5lc2NoL2Nnb3N5bWJvbGl6ZXIiCiAgcGFja2FnZXMg\nPSBbIi4iXQogIHJldmlzaW9uID0gIjcwZTFlZTJiMzlkM2I2MTZhNmFiOTk5\nNjgyMGRkZTIyNGMyN2YzNTEiCgpbW3Byb2plY3RzXV0KICBicmFuY2ggPSAi\nbWFzdGVyIgogIG5hbWUgPSAiZ2l0aHViLmNvbS9iZW9ybjcvcGVya3MiCiAg\ncGFja2FnZXMgPSBbInF1YW50aWxlIl0KICByZXZpc2lvbiA9ICIzYTc3MWQ5\nOTI5NzNmMjRhYTcyNWQwNzg2OGI0NjdkMWRkZmNlYWZiIgoKW1twcm9qZWN0\nc11dCiAgYnJhbmNoID0gIm1hc3RlciIKICBuYW1lID0gImdpdGh1Yi5jb20v\nYmlvZ28vc3RvcmUiCiAgcGFja2FnZXMgPSBbImxscmIiXQogIHJldmlzaW9u\nID0gIjkxMzQyN2ExZDVlODk2MDRlNTBlYTFkYjBmMjhmMzQ5NjZkNjE2MDIi\nCgpbW3Byb2plY3RzXV0KICBuYW1lID0gImdpdGh1Yi5jb20vYm9sdGRiL2Jv\nbHQiCiAgcGFja2FnZXMgPSBbIi4iXQogIHJldmlzaW9uID0gIjJmMWNlN2E4\nMzdkY2I4ZGEzZWM1OTViMWRhYzlkMDYzMmYwZjk5ZTgiCiAgdmVyc2lvbiA9\nICJ2MS4zLjEiCgpbW3Byb2plY3RzXV0KICBuYW1lID0gImdpdGh1Yi5jb20v\nY2Vuay9iYWNrb2ZmIgogIHBhY2thZ2VzID0gWyIuIl0KICByZXZpc2lvbiA9\nICI2MTE1M2M3NjhmMzFlZTVmMTMwMDcxZDA4ZmM4MmI4NTIwODUyOGRlIgog\nIHZlcnNpb24gPSAidjEuMS4wIgoKW1twcm9qZWN0c11dCiAgbmFtZSA9ICJn\naXRodWIuY29tL2NlcnRpZmkvZ29jZXJ0aWZpIgogIHBhY2thZ2VzID0gWyIu\nIl0KICByZXZpc2lvbiA9ICIzZmQ5ZTFhZGIxMmI3MmQyZjNmODIxOTFkNDli\nZTliOTNjNjlmNjdjIgogIHZlcnNpb24gPSAiMjAxNy4wNy4yNyIKCltbcHJv\namVjdHNdXQogIG5hbWUgPSAiZ2l0aHViLmNvbS9jbGllbnQ5L21pc3NwZWxs\nIgogIHBhY2thZ2VzID0gWwogICAgIi4iLAogICAgImNtZC9taXNzcGVsbCIs\nCiAgXQogIHJldmlzaW9uID0gIjU5ODk0YWJkZTkzMWEzMjYzMGQ0ZTg4NGEw\nOWM2ODJlZDIwYzVjN2MiCiAgdmVyc2lvbiA9ICJ2MC4zLjAiCgpbW3Byb2pl\nY3RzXV0KICBuYW1lID0gImdpdGh1Yi5jb20vY29ja3JvYWNoZGIvYXBkIgog\nIHBhY2thZ2VzID0gWyIuIl0KICByZXZpc2lvbiA9ICJiMWNlNDljYjJhNDc0\nZjQ0MTY1MzFlNzM5NTM3M2VhYWZhYTRmYmUyIgogIHZlcnNpb24gPSAidjEu\nMC4wIgoKW1twcm9qZWN0c11dCiAgYnJhbmNoID0gIm1hc3RlciIKICBuYW1l\nID0gImdpdGh1Yi5jb20vY29ja3JvYWNoZGIvY211eCIKICBwYWNrYWdlcyA9\nIFsiLiJdCiAgcmV2aXNpb24gPSAiMzBkMTBiZTQ5MjkyN2UyZGNhZTAwODlj\nMzc0YzQ1NWQ0MjQxNGZjYiIKCltbcHJvamVjdHNdXQogIGJyYW5jaCA9ICJt\nYXN0ZXIiCiAgbmFtZSA9ICJnaXRodWIuY29tL2NvY2tyb2FjaGRiL2NvY2ty\nb2FjaC1nbyIKICBwYWNrYWdlcyA9IFsiY3JkYiJdCiAgcmV2aXNpb24gPSAi\nNTljMDU2MDQ3OGI3MDViZjliZDEyZjkyNTIyMjRhMGZhZDdjODdkZiIKCltb\ncHJvamVjdHNdXQogIGJyYW5jaCA9ICJtYXN0ZXIiCiAgbmFtZSA9ICJnaXRo\ndWIuY29tL2NvY2tyb2FjaGRiL2NybGZtdCIKICBwYWNrYWdlcyA9IFsiLiJd\nCiAgcmV2aXNpb24gPSAiNTg5NTYwN2U1ZWE3OTBmY2JiNjQwMjYwMTI5YTEy\nZDI3N2IzNGU0NiIKCltbcHJvamVjdHNdXQogIGJyYW5jaCA9ICJtYXN0ZXIi\nCiAgbmFtZSA9ICJnaXRodWIuY29tL2NvY2tyb2FjaGRiL3JldHVybmNoZWNr\nIgogIHBhY2thZ2VzID0gWyIuIl0KICByZXZpc2lvbiA9ICJlOTFiYjI4YmFm\nOWRlNGE1MzBkM2FlN2YwNDE5NTNiMjNkY2NlOWJlIgoKW1twcm9qZWN0c11d\nCiAgYnJhbmNoID0gIm1hc3RlciIKICBuYW1lID0gImdpdGh1Yi5jb20vY29j\na3JvYWNoZGIvc3RyZXNzIgogIHBhY2thZ2VzID0gWyIuIl0KICByZXZpc2lv\nbiA9ICIyOWI1ZDMxYjRjM2E5NDljYjNhNzI2NzUwYmMzNGM0ZDU4ZWMxNWU4\nIgoKW1twcm9qZWN0c11dCiAgYnJhbmNoID0gIm1hc3RlciIKICBuYW1lID0g\nImdpdGh1Yi5jb20vY29ja3JvYWNoZGIvdHR5Y29sb3IiCiAgcGFja2FnZXMg\nPSBbIi4iXQogIHJldmlzaW9uID0gIjViZWQyYjVhODc1Yzg4YTlkODdkMGYx\nMmIyMWJhMTU2ZTJmOTk1ZjciCgpbW3Byb2plY3RzXV0KICBicmFuY2ggPSAi\nbWFzdGVyIgogIG5hbWUgPSAiZ2l0aHViLmNvbS9jb2RhaGFsZS9oZHJoaXN0\nb2dyYW0iCiAgcGFja2FnZXMgPSBbIi4iXQogIHJldmlzaW9uID0gIjNhMGJi\nNzc0MjliZDNhNjE1OTZmNWU4YTMxNzI0NDU4NDQzNDIxMjAiCgpbW3Byb2pl\nY3RzXV0KICBicmFuY2ggPSAibWFzdGVyIgogIG5hbWUgPSAiZ2l0aHViLmNv\nbS9jb3Jlb3MvZXRjZCIKICBwYWNrYWdlcyA9IFsKICAgICJyYWZ0IiwKICAg\nICJyYWZ0L3JhZnRwYiIsCiAgXQogIHJldmlzaW9uID0gImNlMGFkMzc3ZDIx\nODE5NTkyYzNkOGMyOTQxN2Q5ZDdhNWFjNTNmMmYiCgpbW3Byb2plY3RzXV0K\nICBuYW1lID0gImdpdGh1Yi5jb20vY3B1Z3V5ODMvZ28tbWQybWFuIgogIHBh\nY2thZ2VzID0gWyJtZDJtYW4iXQogIHJldmlzaW9uID0gIjFkOTAzZGNiNzQ5\nOTkyZjM3NDFkNzQ0YzBmODM3NmI0YmQ3ZWIzZTEiCiAgdmVyc2lvbiA9ICJ2\nMS4wLjciCgpbW3Byb2plY3RzXV0KICBuYW1lID0gImdpdGh1Yi5jb20vZGF2\nZWNnaC9nby1zcGV3IgogIHBhY2thZ2VzID0gWyJzcGV3Il0KICByZXZpc2lv\nbiA9ICIzNDY5MzhkNjQyZjJlYzM1OTRlZDgxZDg3NDQ2MTk2MWNkMGZhYTc2\nIgogIHZlcnNpb24gPSAidjEuMS4wIgoKW1twcm9qZWN0c11dCiAgbmFtZSA9\nICJnaXRodWIuY29tL2RncmlqYWx2YS9qd3QtZ28iCiAgcGFja2FnZXMgPSBb\nIi4iXQogIHJldmlzaW9uID0gImRiZWFhOTMzMmYxOWE5NDRhY2I1NzM2YjQ0\nNTZjZmNjMDIxNDBlMjkiCiAgdmVyc2lvbiA9ICJ2My4xLjAiCgpbW3Byb2pl\nY3RzXV0KICBicmFuY2ggPSAibWFzdGVyIgogIG5hbWUgPSAiZ2l0aHViLmNv\nbS9kZ3J5c2tpL2dvLW1ldHJvIgogIHBhY2thZ2VzID0gWyIuIl0KICByZXZp\nc2lvbiA9ICIyODBmNjA2MmI1YmM5N2VlOWI5YWZlN2YyY2NiMzYxZTU5ODQ1\nYmFhIgoKW1twcm9qZWN0c11dCiAgYnJhbmNoID0gIm1hc3RlciIKICBuYW1l\nID0gImdpdGh1Yi5jb20vZG9ja2VyL2Rpc3RyaWJ1dGlvbiIKICBwYWNrYWdl\ncyA9IFsKICAgICJkaWdlc3RzZXQiLAogICAgInJlZmVyZW5jZSIsCiAgXQog\nIHJldmlzaW9uID0gIjM4MDAwNTZiODgzMmNmNjA3NWU3OGIyODJhYzAxMDEz\nMWQ4Njg3YmMiCgpbW3Byb2plY3RzXV0KICBicmFuY2ggPSAibWFzdGVyIgog\nIG5hbWUgPSAiZ2l0aHViLmNvbS9kb2NrZXIvZG9ja2VyIgogIHBhY2thZ2Vz\nID0gWwogICAgImFwaSIsCiAgICAiYXBpL3R5cGVzIiwKICAgICJhcGkvdHlw\nZXMvYmxraW9kZXYiLAogICAgImFwaS90eXBlcy9jb250YWluZXIiLAogICAg\nImFwaS90eXBlcy9ldmVudHMiLAogICAgImFwaS90eXBlcy9maWx0ZXJzIiwK\nICAgICJhcGkvdHlwZXMvaW1hZ2UiLAogICAgImFwaS90eXBlcy9tb3VudCIs\nCiAgICAiYXBpL3R5cGVzL25ldHdvcmsiLAogICAgImFwaS90eXBlcy9yZWdp\nc3RyeSIsCiAgICAiYXBpL3R5cGVzL3N0cnNsaWNlIiwKICAgICJhcGkvdHlw\nZXMvc3dhcm0iLAogICAgImFwaS90eXBlcy9zd2FybS9ydW50aW1lIiwKICAg\nICJhcGkvdHlwZXMvdGltZSIsCiAgICAiYXBpL3R5cGVzL3ZlcnNpb25zIiwK\nICAgICJhcGkvdHlwZXMvdm9sdW1lIiwKICAgICJjbGllbnQiLAogICAgInBr\nZy9qc29ubWVzc2FnZSIsCiAgICAicGtnL3N0ZGNvcHkiLAogICAgInBrZy90\nZXJtIiwKICAgICJwa2cvdGVybS93aW5kb3dzIiwKICBdCiAgcmV2aXNpb24g\nPSAiM2FiMjBhODdmYTEwMzYwZGY4OTFhNjdiODMwNzM5OWNkZjFhYjQyOSIK\nCltbcHJvamVjdHNdXQogIG5hbWUgPSAiZ2l0aHViLmNvbS9kb2NrZXIvZ28t\nY29ubmVjdGlvbnMiCiAgcGFja2FnZXMgPSBbCiAgICAibmF0IiwKICAgICJz\nb2NrZXRzIiwKICAgICJ0bHNjb25maWciLAogIF0KICByZXZpc2lvbiA9ICIz\nZWRlMzJlMjAzM2RlNzUwNWU2NTAwZDZjODY4YzJiOWVkOWYxNjlkIgogIHZl\ncnNpb24gPSAidjAuMy4wIgoKW1twcm9qZWN0c11dCiAgbmFtZSA9ICJnaXRo\ndWIuY29tL2RvY2tlci9nby11bml0cyIKICBwYWNrYWdlcyA9IFsiLiJdCiAg\ncmV2aXNpb24gPSAiMGRhZGJiMDM0NWIzNWVjN2VmMzVlMjI4ZGFiYjhkZTg5\nYTY1YmY1MiIKICB2ZXJzaW9uID0gInYwLjMuMiIKCltbcHJvamVjdHNdXQog\nIGJyYW5jaCA9ICJtYXN0ZXIiCiAgbmFtZSA9ICJnaXRodWIuY29tL2R1c3Rp\nbi9nby1odW1hbml6ZSIKICBwYWNrYWdlcyA9IFsiLiJdCiAgcmV2aXNpb24g\nPSAiNzdlZDgwNzgzMGI0ZGY1ODE0MTdlN2Y4OWViODFkNDg3MjgzMmI3MiIK\nCltbcHJvamVjdHNdXQogIG5hbWUgPSAiZ2l0aHViLmNvbS9lYXBhY2hlL2dv\nLXJlc2lsaWVuY3kiCiAgcGFja2FnZXMgPSBbImJyZWFrZXIiXQogIHJldmlz\naW9uID0gIjY4MDA0ODJmMmM4MTNlNjg5Yzg4YjdlZDMyODIyNjIzODUwMTE4\nOTAiCiAgdmVyc2lvbiA9ICJ2MS4wLjAiCgpbW3Byb2plY3RzXV0KICBicmFu\nY2ggPSAibWFzdGVyIgogIG5hbWUgPSAiZ2l0aHViLmNvbS9lYXBhY2hlL2dv\nLXhlcmlhbC1zbmFwcHkiCiAgcGFja2FnZXMgPSBbIi4iXQogIHJldmlzaW9u\nID0gImJiOTU1ZTAxYjkzNDZhYzE5ZGMyOWViMTY1ODZjOTBkZWQ5OWE5OGMi\nCgpbW3Byb2plY3RzXV0KICBuYW1lID0gImdpdGh1Yi5jb20vZWFwYWNoZS9x\ndWV1ZSIKICBwYWNrYWdlcyA9IFsiLiJdCiAgcmV2aXNpb24gPSAiZGVkNTk1\nOWMwZDRlMzYwNjQ2ZGM5ZTk5MDhjZmY0ODY2Njc4MTM2NyIKICB2ZXJzaW9u\nID0gInYxLjAuMiIKCltbcHJvamVjdHNdXQogIG5hbWUgPSAiZ2l0aHViLmNv\nbS9lbGFzdGljL2dvc2lnYXIiCiAgcGFja2FnZXMgPSBbCiAgICAiLiIsCiAg\nICAic3lzL3dpbmRvd3MiLAogIF0KICByZXZpc2lvbiA9ICIzMDZkNTE5ODE3\nODljY2M2NWU1ZjE0MzFkNWMwZDc4ZDhjMzY4ZjFiIgogIHZlcnNpb24gPSAi\ndjAuNS4wIgoKW1twcm9qZWN0c11dCiAgbmFtZSA9ICJnaXRodWIuY29tL2Vs\nYXphcmwvZ28tYmluZGF0YS1hc3NldGZzIgogIHBhY2thZ2VzID0gWyIuIl0K\nICByZXZpc2lvbiA9ICIzMGY4MmZhMjNmZDg0NGJkNWJiMWU1ZjIxNmRiODdm\nZDc3YjVlYjQzIgogIHZlcnNpb24gPSAidjEuMC4wIgoKW1twcm9qZWN0c11d\nCiAgYnJhbmNoID0gIm1hc3RlciIKICBuYW1lID0gImdpdGh1Yi5jb20vZmFj\nZWJvb2tnby9jbG9jayIKICBwYWNrYWdlcyA9IFsiLiJdCiAgcmV2aXNpb24g\nPSAiNjAwZDg5OGFmNDBhYTA5YTdhOTNlY2I5MjY1ZDg3YjA1MDRiNmYwMyIK\nCltbcHJvamVjdHNdXQogIGJyYW5jaCA9ICJtYXN0ZXIiCiAgbmFtZSA9ICJn\naXRodWIuY29tL2dldHNlbnRyeS9yYXZlbi1nbyIKICBwYWNrYWdlcyA9IFsi\nLiJdCiAgcmV2aXNpb24gPSAiMjIxYjJiNDRmYjMzZjg0ZWQzZWExM2YzYWVk\nNjJmZjQ4Yzg1NjM2YiIKICBzb3VyY2UgPSAiaHR0cHM6Ly9naXRodWIuY29t\nL2NvY2tyb2FjaGRiL3JhdmVuLWdvIgoKW1twcm9qZWN0c11dCiAgYnJhbmNo\nID0gIm1hc3RlciIKICBuYW1lID0gImdpdGh1Yi5jb20vZ2hlbWF3YXQvc3Ry\nZWFtIgogIHBhY2thZ2VzID0gWyIuIl0KICByZXZpc2lvbiA9ICI3OGU2ODJh\nYmNhZTRmOTZhYzdkZGJlMzk5MTI5NjdhNWY3Y2JiYWE2IgoKW1twcm9qZWN0\nc11dCiAgbmFtZSA9ICJnaXRodWIuY29tL2dvLWluaS9pbmkiCiAgcGFja2Fn\nZXMgPSBbIi4iXQogIHJldmlzaW9uID0gImYyODBiM2JhNTE3YmY1ZmM5ODky\nMjYyNGYyMWZiMGU3YTkyYWRhZWMiCiAgdmVyc2lvbiA9ICJ2MS4zMC4zIgoK\nW1twcm9qZWN0c11dCiAgbmFtZSA9ICJnaXRodWIuY29tL2dvLWxvZ2ZtdC9s\nb2dmbXQiCiAgcGFja2FnZXMgPSBbIi4iXQogIHJldmlzaW9uID0gIjM5MGFi\nNzkzNWVlMjhlYzZiMjg2MzY0YmJhOWI0ZGQ2NDEwY2IzZDUiCiAgdmVyc2lv\nbiA9ICJ2MC4zLjAiCgpbW3Byb2plY3RzXV0KICBuYW1lID0gImdpdGh1Yi5j\nb20vZ28tb2xlL2dvLW9sZSIKICBwYWNrYWdlcyA9IFsKICAgICIuIiwKICAg\nICJvbGV1dGlsIiwKICBdCiAgcmV2aXNpb24gPSAiYTQxZTNjNGI3MDZmNmFl\nOGRmYmZmMzQyYjA2ZTQwZmE0ZDJkMDUwNiIKICB2ZXJzaW9uID0gInYxLjIu\nMSIKCltbcHJvamVjdHNdXQogIGJyYW5jaCA9ICJtYXN0ZXIiCiAgbmFtZSA9\nICJnaXRodWIuY29tL2dvLXNxbC1kcml2ZXIvbXlzcWwiCiAgcGFja2FnZXMg\nPSBbIi4iXQogIHJldmlzaW9uID0gImZhZGUyMTAwOTc5NzE1OGU3Yjc5ZTA0\nYzM0MDExOGE5MjIwYzZmOWUiCgpbW3Byb2plY3RzXV0KICBicmFuY2ggPSAi\ndjIiCiAgbmFtZSA9ICJnaXRodWIuY29tL2dvLXlhbWwveWFtbCIKICBwYWNr\nYWdlcyA9IFsiLiJdCiAgcmV2aXNpb24gPSAiZWIzNzMzZDE2MGU3NGE5Yzdl\nNDQyZjQzNWViM2JlYTQ1OGUxZDE5ZiIKCltbcHJvamVjdHNdXQogIG5hbWUg\nPSAiZ2l0aHViLmNvbS9nb2dvL3Byb3RvYnVmIgogIHBhY2thZ2VzID0gWwog\nICAgImdvZ29wcm90byIsCiAgICAianNvbnBiIiwKICAgICJwbHVnaW4vY29t\ncGFyZSIsCiAgICAicGx1Z2luL2RlZmF1bHRjaGVjayIsCiAgICAicGx1Z2lu\nL2Rlc2NyaXB0aW9uIiwKICAgICJwbHVnaW4vZW1iZWRjaGVjayIsCiAgICAi\ncGx1Z2luL2VudW1zdHJpbmdlciIsCiAgICAicGx1Z2luL2VxdWFsIiwKICAg\nICJwbHVnaW4vZmFjZSIsCiAgICAicGx1Z2luL2dvc3RyaW5nIiwKICAgICJw\nbHVnaW4vbWFyc2hhbHRvIiwKICAgICJwbHVnaW4vb25lb2ZjaGVjayIsCiAg\nICAicGx1Z2luL3BvcHVsYXRlIiwKICAgICJwbHVnaW4vc2l6ZSIsCiAgICAi\ncGx1Z2luL3N0cmluZ2VyIiwKICAgICJwbHVnaW4vdGVzdGdlbiIsCiAgICAi\ncGx1Z2luL3VuaW9uIiwKICAgICJwbHVnaW4vdW5tYXJzaGFsIiwKICAgICJw\ncm90byIsCiAgICAicHJvdG9jLWdlbi1nb2dvL2Rlc2NyaXB0b3IiLAogICAg\nInByb3RvYy1nZW4tZ29nby9nZW5lcmF0b3IiLAogICAgInByb3RvYy1nZW4t\nZ29nby9ncnBjIiwKICAgICJwcm90b2MtZ2VuLWdvZ28vcGx1Z2luIiwKICAg\nICJzb3J0a2V5cyIsCiAgICAidHlwZXMiLAogICAgInZhbml0eSIsCiAgICAi\ndmFuaXR5L2NvbW1hbmQiLAogIF0KICByZXZpc2lvbiA9ICIxYWRmYzEyNmI0\nMTUxM2NjNjk2YjIwOTY2N2M4NjU2ZWE3YWFjNjdjIgogIHZlcnNpb24gPSAi\ndjEuMC4wIgoKW1twcm9qZWN0c11dCiAgYnJhbmNoID0gIm1hc3RlciIKICBu\nYW1lID0gImdpdGh1Yi5jb20vZ29sYW5nLWNvbW1vbm1hcmsvbWFya2Rvd24i\nCiAgcGFja2FnZXMgPSBbCiAgICAiLiIsCiAgICAiYnl0ZXV0aWwiLAogICAg\nImh0bWwiLAogICAgImxpbmtpZnkiLAogIF0KICByZXZpc2lvbiA9ICIxMWE3\nYTgzOWU3MjNhYTI5M2NjY2RjMzUzYjM5NGRiZmNlN2MxMzFlIgoKW1twcm9q\nZWN0c11dCiAgbmFtZSA9ICJnaXRodWIuY29tL2dvbGFuZy9kZXAiCiAgcGFj\na2FnZXMgPSBbCiAgICAiLiIsCiAgICAiY21kL2RlcCIsCiAgICAiZ3BzIiwK\nICAgICJncHMvaW50ZXJuYWwvcGIiLAogICAgImdwcy9wYXRocyIsCiAgICAi\nZ3BzL3BrZ3RyZWUiLAogICAgImludGVybmFsL2ZlZWRiYWNrIiwKICAgICJp\nbnRlcm5hbC9mcyIsCiAgICAiaW50ZXJuYWwvaW1wb3J0ZXJzIiwKICAgICJp\nbnRlcm5hbC9pbXBvcnRlcnMvYmFzZSIsCiAgICAiaW50ZXJuYWwvaW1wb3J0\nZXJzL2dsaWRlIiwKICAgICJpbnRlcm5hbC9pbXBvcnRlcnMvZ2xvY2siLAog\nICAgImludGVybmFsL2ltcG9ydGVycy9nb2RlcCIsCiAgICAiaW50ZXJuYWwv\naW1wb3J0ZXJzL2dvdmVuZCIsCiAgICAiaW50ZXJuYWwvaW1wb3J0ZXJzL2dv\ndmVuZG9yIiwKICAgICJpbnRlcm5hbC9pbXBvcnRlcnMvZ3Z0IiwKICAgICJp\nbnRlcm5hbC9pbXBvcnRlcnMvdm5kciIsCiAgXQogIHJldmlzaW9uID0gIjM3\nZDllYTBhYzE2ZjBlMGEwNWFmYzNiNjBlMWFjOGMzNjRiNmMzMjkiCiAgdmVy\nc2lvbiA9ICJ2MC40LjEiCgpbW3Byb2plY3RzXV0KICBicmFuY2ggPSAibWFz\ndGVyIgogIG5hbWUgPSAiZ2l0aHViLmNvbS9nb2xhbmcvZ2xvZyIKICBwYWNr\nYWdlcyA9IFsiLiJdCiAgcmV2aXNpb24gPSAiMjNkZWY0ZTZjMTRiNGRhOGFj\nMmVkODAwNzMzN2JjNWViNTAwNzk5OCIKCltbcHJvamVjdHNdXQogIGJyYW5j\naCA9ICJtYXN0ZXIiCiAgbmFtZSA9ICJnaXRodWIuY29tL2dvbGFuZy9sZXZl\nbGRiIgogIHBhY2thZ2VzID0gWwogICAgImNyYyIsCiAgICAiZGIiLAogICAg\nIm1lbWZzIiwKICAgICJ0YWJsZSIsCiAgXQogIHJldmlzaW9uID0gIjI1OWQ5\nMjUzZDcxOTk2Yjc3NzhhM2VmYjQxNDRmZTQ4OTIzNDJiMTgiCgpbW3Byb2pl\nY3RzXV0KICBicmFuY2ggPSAibWFzdGVyIgogIG5hbWUgPSAiZ2l0aHViLmNv\nbS9nb2xhbmcvbGludCIKICBwYWNrYWdlcyA9IFsKICAgICIuIiwKICAgICJn\nb2xpbnQiLAogIF0KICByZXZpc2lvbiA9ICI2YWFmN2MzNGFmMGY0YzM2YTU3\nZTBjNDI5YmFjZTRkNzA2ZDhlOTMxIgoKW1twcm9qZWN0c11dCiAgYnJhbmNo\nID0gIm1hc3RlciIKICBuYW1lID0gImdpdGh1Yi5jb20vZ29sYW5nL3Byb3Rv\nYnVmIgogIHBhY2thZ2VzID0gWwogICAgImpzb25wYiIsCiAgICAicHJvdG8i\nLAogICAgInByb3RvYy1nZW4tZ28vZGVzY3JpcHRvciIsCiAgICAicHJvdG9j\nLWdlbi1nby9nZW5lcmF0b3IiLAogICAgInByb3RvYy1nZW4tZ28vZ2VuZXJh\ndG9yL2ludGVybmFsL3JlbWFwIiwKICAgICJwcm90b2MtZ2VuLWdvL3BsdWdp\nbiIsCiAgICAicHR5cGVzIiwKICAgICJwdHlwZXMvYW55IiwKICAgICJwdHlw\nZXMvZHVyYXRpb24iLAogICAgInB0eXBlcy9zdHJ1Y3QiLAogICAgInB0eXBl\ncy90aW1lc3RhbXAiLAogIF0KICByZXZpc2lvbiA9ICJiNGRlZGEwOTczZmI0\nYzcwYjUwZDIyNmIxYWY0OWYzZGE1OWY1MjY1IgoKW1twcm9qZWN0c11dCiAg\nYnJhbmNoID0gIm1hc3RlciIKICBuYW1lID0gImdpdGh1Yi5jb20vZ29sYW5n\nL3NuYXBweSIKICBwYWNrYWdlcyA9IFsiLiJdCiAgcmV2aXNpb24gPSAiNTUz\nYTY0MTQ3MDQ5NmIyMzI3YWJjYWMxMGIzNjM5NmJkOThlNDVjOSIKCltbcHJv\namVjdHNdXQogIGJyYW5jaCA9ICJtYXN0ZXIiCiAgbmFtZSA9ICJnaXRodWIu\nY29tL2dvb2dsZS9idHJlZSIKICBwYWNrYWdlcyA9IFsiLiJdCiAgcmV2aXNp\nb24gPSAiZTg5MzczZmU2YjRhNzQxM2Q3YWNkNmRhMTcyNWI4M2VmNzEzZTZl\nNCIKCltbcHJvamVjdHNdXQogIG5hbWUgPSAiZ2l0aHViLmNvbS9nb29nbGUv\nZ28tZ2l0aHViIgogIHBhY2thZ2VzID0gWyJnaXRodWIiXQogIHJldmlzaW9u\nID0gImU0ODA2MGEyOGZhYzUyZDBmMWNiNzU4YmM4Yjg3YzA3YmFjNGE4N2Qi\nCiAgdmVyc2lvbiA9ICJ2MTUuMC4wIgoKW1twcm9qZWN0c11dCiAgYnJhbmNo\nID0gIm1hc3RlciIKICBuYW1lID0gImdpdGh1Yi5jb20vZ29vZ2xlL2dvLXF1\nZXJ5c3RyaW5nIgogIHBhY2thZ2VzID0gWyJxdWVyeSJdCiAgcmV2aXNpb24g\nPSAiNTNlNmNlMTE2MTM1YjgwZDAzNzkyMWE3ZmRkNTEzOGNmMzJkN2E4YSIK\nCltbcHJvamVjdHNdXQogIGJyYW5jaCA9ICJtYXN0ZXIiCiAgbmFtZSA9ICJn\naXRodWIuY29tL2dvb2dsZS9wcHJvZiIKICBwYWNrYWdlcyA9IFsKICAgICIu\nIiwKICAgICJkcml2ZXIiLAogICAgImludGVybmFsL2JpbnV0aWxzIiwKICAg\nICJpbnRlcm5hbC9kcml2ZXIiLAogICAgImludGVybmFsL2VsZmV4ZWMiLAog\nICAgImludGVybmFsL2dyYXBoIiwKICAgICJpbnRlcm5hbC9tZWFzdXJlbWVu\ndCIsCiAgICAiaW50ZXJuYWwvcGx1Z2luIiwKICAgICJpbnRlcm5hbC9yZXBv\ncnQiLAogICAgImludGVybmFsL3N5bWJvbGl6ZXIiLAogICAgImludGVybmFs\nL3N5bWJvbHoiLAogICAgInByb2ZpbGUiLAogICAgInRoaXJkX3BhcnR5L2Qz\nIiwKICAgICJ0aGlyZF9wYXJ0eS9kM2ZsYW1lZ3JhcGgiLAogICAgInRoaXJk\nX3BhcnR5L2QzdGlwIiwKICAgICJ0aGlyZF9wYXJ0eS9zdmdwYW4iLAogIF0K\nICByZXZpc2lvbiA9ICI2MDg0MDg5OTYzOGJmYWYzZmQ2ZjJiMTdiOTc4NWYx\nZGFjNjdhNGE0IgoKW1twcm9qZWN0c11dCiAgbmFtZSA9ICJnaXRodWIuY29t\nL2dvb2dsZWFwaXMvZ2F4LWdvIgogIHBhY2thZ2VzID0gWyIuIl0KICByZXZp\nc2lvbiA9ICIzMTdlMDAwNjI1NGM0NGEwYWM0MjdjYzUyYTBlMDgzZmYwYjk2\nMjJmIgogIHZlcnNpb24gPSAidjIuMC4wIgoKW1twcm9qZWN0c11dCiAgbmFt\nZSA9ICJnaXRodWIuY29tL2dvcmlsbGEvd2Vic29ja2V0IgogIHBhY2thZ2Vz\nID0gWyIuIl0KICByZXZpc2lvbiA9ICJlYTRkMWY2ODFiYWJiY2U5NTQ1Yzlj\nNWYzZDUxOTRhNzg5Yzg5ZjViIgogIHZlcnNpb24gPSAidjEuMi4wIgoKW1tw\ncm9qZWN0c11dCiAgbmFtZSA9ICJnaXRodWIuY29tL2dycGMtZWNvc3lzdGVt\nL2dycGMtZ2F0ZXdheSIKICBwYWNrYWdlcyA9IFsKICAgICJwcm90b2MtZ2Vu\nLWdycGMtZ2F0ZXdheSIsCiAgICAicHJvdG9jLWdlbi1ncnBjLWdhdGV3YXkv\nZGVzY3JpcHRvciIsCiAgICAicHJvdG9jLWdlbi1ncnBjLWdhdGV3YXkvZ2Vu\nZXJhdG9yIiwKICAgICJwcm90b2MtZ2VuLWdycGMtZ2F0ZXdheS9nZW5nYXRl\nd2F5IiwKICAgICJwcm90b2MtZ2VuLWdycGMtZ2F0ZXdheS9odHRwcnVsZSIs\nCiAgICAicnVudGltZSIsCiAgICAicnVudGltZS9pbnRlcm5hbCIsCiAgICAi\ndXRpbGl0aWVzIiwKICBdCiAgcmV2aXNpb24gPSAiMDdmNWU3OTc2ODAyMmY5\nYTMyNjUyMzVmMGRiNGFjOGMzZjY3NWZlYyIKICB2ZXJzaW9uID0gInYxLjMu\nMSIKCltbcHJvamVjdHNdXQogIGJyYW5jaCA9ICJtYXN0ZXIiCiAgbmFtZSA9\nICJnaXRodWIuY29tL2dycGMtZWNvc3lzdGVtL2dycGMtb3BlbnRyYWNpbmci\nCiAgcGFja2FnZXMgPSBbImdvL290Z3JwYyJdCiAgcmV2aXNpb24gPSAiYTU3\nMGFmMzk3MDRiOWYzZDRiYjUzMGQ4MzE5MmE5OWVhNjMzN2Q1YSIKCltbcHJv\namVjdHNdXQogIGJyYW5jaCA9ICJtYXN0ZXIiCiAgbmFtZSA9ICJnaXRodWIu\nY29tL2hhc2hpY29ycC9nby12ZXJzaW9uIgogIHBhY2thZ2VzID0gWyIuIl0K\nICByZXZpc2lvbiA9ICJmYzYxMzg5ZTI3YzcxZDEyMGY4NzAzMWNhOGM4OGEz\nNDI4ZjM3MmRkIgoKW1twcm9qZWN0c11dCiAgYnJhbmNoID0gIm1hc3RlciIK\nICBuYW1lID0gImdpdGh1Yi5jb20vaWFubGFuY2V0YXlsb3IvY2dvc3ltYm9s\naXplciIKICBwYWNrYWdlcyA9IFsiLiJdCiAgcmV2aXNpb24gPSAiZjUwNzJk\nZjljNTUwZGM2ODcxNTdlNWQ3ZWZiNTA4MjVjZGY4ZjBlYiIKCltbcHJvamVj\ndHNdXQogIGJyYW5jaCA9ICJtYXN0ZXIiCiAgbmFtZSA9ICJnaXRodWIuY29t\nL2lhbmxhbmNldGF5bG9yL2RlbWFuZ2xlIgogIHBhY2thZ2VzID0gWyIuIl0K\nICByZXZpc2lvbiA9ICI0ODgzMjI3ZjY2MzcxZTAyYzQ5NDg5MzdkM2UyYmUx\nNjY0ZDliZTM4IgoKW1twcm9qZWN0c11dCiAgbmFtZSA9ICJnaXRodWIuY29t\nL2luY29uc2hyZXZlYWJsZS9tb3VzZXRyYXAiCiAgcGFja2FnZXMgPSBbIi4i\nXQogIHJldmlzaW9uID0gIjc2NjI2YWU5YzkxYzRmMmExMGYzNGNhZDhjZTgz\nZWE0MmM5M2JiNzUiCiAgdmVyc2lvbiA9ICJ2MS4wIgoKW1twcm9qZWN0c11d\nCiAgbmFtZSA9ICJnaXRodWIuY29tL2phY2tjL3BneCIKICBwYWNrYWdlcyA9\nIFsKICAgICIuIiwKICAgICJjaHVua3JlYWRlciIsCiAgICAiaW50ZXJuYWwv\nc2FuaXRpemUiLAogICAgInBnaW8iLAogICAgInBncHJvdG8zIiwKICAgICJw\nZ3R5cGUiLAogIF0KICByZXZpc2lvbiA9ICJkYTMyMzFiMGI2NmUyZTc0Y2Ri\nNzc5ZjFkNDZjNWU5NThiYThiZTI3IgogIHZlcnNpb24gPSAidjMuMS4wIgoK\nW1twcm9qZWN0c11dCiAgbmFtZSA9ICJnaXRodWIuY29tL2ptYW5rODgvbnV0\ncyIKICBwYWNrYWdlcyA9IFsiLiJdCiAgcmV2aXNpb24gPSAiOGIyODE0NWRm\nZmM4NzEwNGU2NmQwNzRmNjJlYTgwODBlZGZhZDdjOCIKICB2ZXJzaW9uID0g\nInYwLjMuMCIKCltbcHJvamVjdHNdXQogIG5hbWUgPSAiZ2l0aHViLmNvbS9q\nbWVzcGF0aC9nby1qbWVzcGF0aCIKICBwYWNrYWdlcyA9IFsiLiJdCiAgcmV2\naXNpb24gPSAiMGIxMmQ2YjUiCgpbW3Byb2plY3RzXV0KICBicmFuY2ggPSAi\nbWFzdGVyIgogIG5hbWUgPSAiZ2l0aHViLmNvbS9qdGVldXdlbi9nby1iaW5k\nYXRhIgogIHBhY2thZ2VzID0gWwogICAgIi4iLAogICAgImdvLWJpbmRhdGEi\nLAogIF0KICByZXZpc2lvbiA9ICI2YjY2N2Y4NTE5NmU4MGQ4NDIwZDYzNGNh\nM2ZlY2NhMzg5YTUzMjA3IgogIHNvdXJjZSA9ICJodHRwczovL2dpdGh1Yi5j\nb20vZGltMTMvZ28tYmluZGF0YSIKCltbcHJvamVjdHNdXQogIGJyYW5jaCA9\nICJtYXN0ZXIiCiAgbmFtZSA9ICJnaXRodWIuY29tL2tpc2llbGsvZXJyY2hl\nY2siCiAgcGFja2FnZXMgPSBbCiAgICAiLiIsCiAgICAiaW50ZXJuYWwvZXJy\nY2hlY2siLAogIF0KICByZXZpc2lvbiA9ICJiMTQ0NWE5ZGQ4Mjg1YTUwYzZk\nMTY2MWQxNmYwYTljZWIwODEyNWY3IgoKW1twcm9qZWN0c11dCiAgYnJhbmNo\nID0gIm1hc3RlciIKICBuYW1lID0gImdpdGh1Yi5jb20va2lzaWVsay9nb3Rv\nb2wiCiAgcGFja2FnZXMgPSBbCiAgICAiLiIsCiAgICAiaW50ZXJuYWwvbG9h\nZCIsCiAgXQogIHJldmlzaW9uID0gImQ2Y2U2MjYyZDg3ZTNhNGUxNTNlODYw\nMjNmZjU2YWU3NzE1NTRhNDEiCgpbW3Byb2plY3RzXV0KICBuYW1lID0gImdp\ndGh1Yi5jb20va256L2dvLWxpYmVkaXQiCiAgcGFja2FnZXMgPSBbCiAgICAi\nLiIsCiAgICAiY29tbW9uIiwKICAgICJvdGhlciIsCiAgICAidW5peCIsCiAg\nICAidW5peC9zaWd0cmFtcCIsCiAgXQogIHJldmlzaW9uID0gIjY5Yjc1OWQ2\nZmY4NGRmNmJiZDM5ODQ2ZGFlMzY3MDYyNWNjMDM2MWIiCiAgdmVyc2lvbiA9\nICJ2MS43IgoKW1twcm9qZWN0c11dCiAgYnJhbmNoID0gIm1hc3RlciIKICBu\nYW1lID0gImdpdGh1Yi5jb20va256L3N0cnRpbWUiCiAgcGFja2FnZXMgPSBb\nIi4iXQogIHJldmlzaW9uID0gIjgxMzcyNWE3YzE4M2FjMzA0ZDFlZDhmYjdl\nMzdhNTY3NzMzZTljMDUiCgpbW3Byb2plY3RzXV0KICBicmFuY2ggPSAibWFz\ndGVyIgogIG5hbWUgPSAiZ2l0aHViLmNvbS9rci9sb2dmbXQiCiAgcGFja2Fn\nZXMgPSBbIi4iXQogIHJldmlzaW9uID0gImI4NGUzMGFjZDUxNWFhZGM0Yjc4\nM2FkNGZmODNhZmYzMjk5YmRmZTAiCgpbW3Byb2plY3RzXV0KICBicmFuY2gg\nPSAibWFzdGVyIgogIG5hbWUgPSAiZ2l0aHViLmNvbS9rci9wcmV0dHkiCiAg\ncGFja2FnZXMgPSBbIi4iXQogIHJldmlzaW9uID0gImNmYjU1YWFmZGFmM2Vj\nMDhmMGRiMjI2OTlhYjgyMmM1MDA5MWIxYzQiCgpbW3Byb2plY3RzXV0KICBi\ncmFuY2ggPSAibWFzdGVyIgogIG5hbWUgPSAiZ2l0aHViLmNvbS9rci90ZXh0\nIgogIHBhY2thZ2VzID0gWyIuIl0KICByZXZpc2lvbiA9ICI3Y2FmY2Q4Mzc4\nNDRlNzg0YjUyNjM2OWM5YmNlMjYyODA0YWViYzYwIgoKW1twcm9qZWN0c11d\nCiAgYnJhbmNoID0gIm1hc3RlciIKICBuYW1lID0gImdpdGh1Yi5jb20vbGli\nL3BxIgogIHBhY2thZ2VzID0gWwogICAgIi4iLAogICAgIm9pZCIsCiAgXQog\nIHJldmlzaW9uID0gIjI3ZWE1ZDkyZGUzMDA2MGU3MTIxZGRkNTQzZmUxNGU5\nYTMyN2UwY2MiCgpbW3Byb2plY3RzXV0KICBuYW1lID0gImdpdGh1Yi5jb20v\nbGlnaHRzdGVwL2xpZ2h0c3RlcC10cmFjZXItZ28iCiAgcGFja2FnZXMgPSBb\nCiAgICAiLiIsCiAgICAiY29sbGVjdG9ycGIiLAogICAgImxpZ2h0c3RlcF90\naHJpZnQiLAogICAgImxpZ2h0c3RlcHBiIiwKICAgICJ0aHJpZnRfMF85XzIv\nbGliL2dvL3RocmlmdCIsCiAgXQogIHJldmlzaW9uID0gImJhMzhiYWUxZjBl\nYzFlY2U5MDkyZDM1YmJlY2JiNDk3ZWUzNDRjYmMiCiAgdmVyc2lvbiA9ICJ2\nMC4xNS4wIgoKW1twcm9qZWN0c11dCiAgbmFtZSA9ICJnaXRodWIuY29tL21h\ncnN0ci9ndWlkIgogIHBhY2thZ2VzID0gWyIuIl0KICByZXZpc2lvbiA9ICI4\nYmQ5YTY0YmYzN2ViMjk3YjQ5MmE0MTAxZmIyOGU4MGFjMGIyOTBmIgogIHZl\ncnNpb24gPSAidjEuMS4wIgoKW1twcm9qZWN0c11dCiAgbmFtZSA9ICJnaXRo\ndWIuY29tL21hcnVzYW1hL3NlbWFwaG9yZSIKICBwYWNrYWdlcyA9IFsiLiJd\nCiAgcmV2aXNpb24gPSAiNTY3ZjE3MjA2ZWFhM2YyOGJkNzNlMjA5NjIwZTJh\nYmFhNjVkNzQwNSIKICB2ZXJzaW9uID0gIjIuMS4xIgoKW1twcm9qZWN0c11d\nCiAgbmFtZSA9ICJnaXRodWIuY29tL21hdHRuL2dvLWlzYXR0eSIKICBwYWNr\nYWdlcyA9IFsiLiJdCiAgcmV2aXNpb24gPSAiMDM2MGIyYWY0ZjM4ZThkMzhj\nN2ZjZTJhOWY0ZTcwMjcwMmQ3M2EzOSIKICB2ZXJzaW9uID0gInYwLjAuMyIK\nCltbcHJvamVjdHNdXQogIG5hbWUgPSAiZ2l0aHViLmNvbS9tYXR0bi9nby1y\ndW5ld2lkdGgiCiAgcGFja2FnZXMgPSBbIi4iXQogIHJldmlzaW9uID0gIjll\nNzc3YTgzNjZjY2U2MDUxMzBhNTMxZDJjZDYzNjNkMDdhZDczMTciCiAgdmVy\nc2lvbiA9ICJ2MC4wLjIiCgpbW3Byb2plY3RzXV0KICBuYW1lID0gImdpdGh1\nYi5jb20vbWF0dG4vZ292ZXJhbGxzIgogIHBhY2thZ2VzID0gWyIuIl0KICBy\nZXZpc2lvbiA9ICJiNzFhMWU0ODU1Zjg3OTkxYWZmMDFjMmM4MzNhNzVhMDcw\nNTljNjFjIgogIHZlcnNpb24gPSAidjAuMC4yIgoKW1twcm9qZWN0c11dCiAg\nbmFtZSA9ICJnaXRodWIuY29tL21hdHR0cHJvdWQvZ29sYW5nX3Byb3RvYnVm\nX2V4dGVuc2lvbnMiCiAgcGFja2FnZXMgPSBbInBidXRpbCJdCiAgcmV2aXNp\nb24gPSAiMzI0N2M4NDUwMGJmZjhkOWZiNmQ1NzlkODAwZjIwYjNlMDkxNTgy\nYyIKICB2ZXJzaW9uID0gInYxLjAuMCIKCltbcHJvamVjdHNdXQogIG5hbWUg\nPSAiZ2l0aHViLmNvbS9taWJrL2R1cGwiCiAgcGFja2FnZXMgPSBbCiAgICAi\nLiIsCiAgICAiam9iIiwKICAgICJvdXRwdXQiLAogICAgInN1ZmZpeHRyZWUi\nLAogICAgInN5bnRheCIsCiAgICAic3ludGF4L2dvbGFuZyIsCiAgXQogIHJl\ndmlzaW9uID0gImYwMDhmY2Y1ZTYyNzkzZDM4YmRhNTEwZWUzN2FhYjhiMGM2\nOGU3NmMiCiAgdmVyc2lvbiA9ICJ2MS4wLjAiCgpbW3Byb2plY3RzXV0KICBi\ncmFuY2ggPSAibWFzdGVyIgogIG5hbWUgPSAiZ2l0aHViLmNvbS9taXRjaGVs\nbGgvcmVmbGVjdHdhbGsiCiAgcGFja2FnZXMgPSBbIi4iXQogIHJldmlzaW9u\nID0gIjYzZDYwZTlkMGRiYzYwY2Y5MTY0ZTY1MTA4ODliMGRiNjY4M2Q5OGMi\nCgpbW3Byb2plY3RzXV0KICBicmFuY2ggPSAibWFzdGVyIgogIG5hbWUgPSAi\nZ2l0aHViLmNvbS9tb250YW5hZmx5bm4vc3RhdHMiCiAgcGFja2FnZXMgPSBb\nIi4iXQogIHJldmlzaW9uID0gIjcyNjI1ZWMxNjkxZTQwMTgxYWM1MjgyYTdm\nMmNhNDc0NTk1MmE3YTciCgpbW3Byb2plY3RzXV0KICBicmFuY2ggPSAibWFz\ndGVyIgogIG5hbWUgPSAiZ2l0aHViLmNvbS9uaWdodGx5b25lL2xvY2tmaWxl\nIgogIHBhY2thZ2VzID0gWyIuIl0KICByZXZpc2lvbiA9ICI2YTE5N2Q1ZWE2\nMTE2OGYyYWM4MjFkZTJiN2YwMTFiMjUwOTA0OTAwIgoKW1twcm9qZWN0c11d\nCiAgbmFtZSA9ICJnaXRodWIuY29tL25sb3Blcy9zbGFjayIKICBwYWNrYWdl\ncyA9IFsiLiJdCiAgcmV2aXNpb24gPSAiOGFiNGQwYjM2NGVmMWU5YWY1ZDEw\nMjUzMWRhMjBkNWVjOTAyYjZjNCIKICB2ZXJzaW9uID0gInYwLjIuMCIKCltb\ncHJvamVjdHNdXQogIGJyYW5jaCA9ICJtYXN0ZXIiCiAgbmFtZSA9ICJnaXRo\ndWIuY29tL29sZWt1a29ua28vdGFibGV3cml0ZXIiCiAgcGFja2FnZXMgPSBb\nIi4iXQogIHJldmlzaW9uID0gImI4YTliZTA3MGRhNDA0NDllNTAxYzNjNDcz\nMGE4ODllNDJkODdhOWUiCgpbW3Byb2plY3RzXV0KICBuYW1lID0gImdpdGh1\nYi5jb20vb3BlbmNvbnRhaW5lcnMvZ28tZGlnZXN0IgogIHBhY2thZ2VzID0g\nWyIuIl0KICByZXZpc2lvbiA9ICIyNzliZWQ5ODY3M2RkNWJlZjM3NGQzYjZl\nNGIwOWUyYWY3NjE4M2JmIgogIHZlcnNpb24gPSAidjEuMC4wLXJjMSIKCltb\ncHJvamVjdHNdXQogIG5hbWUgPSAiZ2l0aHViLmNvbS9vcGVuY29udGFpbmVy\ncy9pbWFnZS1zcGVjIgogIHBhY2thZ2VzID0gWwogICAgInNwZWNzLWdvIiwK\nICAgICJzcGVjcy1nby92MSIsCiAgXQogIHJldmlzaW9uID0gImFiNzM4OWVm\nOWY1MDAzMGM5YjI0NWJjMTZiOTgxYzdkZGYxOTI4ODIiCiAgdmVyc2lvbiA9\nICJ2MS4wLjAiCgpbW3Byb2plY3RzXV0KICBicmFuY2ggPSAibWFzdGVyIgog\nIG5hbWUgPSAiZ2l0aHViLmNvbS9vcGVubm90YS91cmxlc2MiCiAgcGFja2Fn\nZXMgPSBbIi4iXQogIHJldmlzaW9uID0gImRlNWJmMmFkNDU3ODQ2Mjk2ZTIw\nMzE0MjFhMzRlMjU2OGUzMDRlMzUiCgpbW3Byb2plY3RzXV0KICBicmFuY2gg\nPSAibWFzdGVyIgogIG5hbWUgPSAiZ2l0aHViLmNvbS9vcGVudHJhY2luZy1j\nb250cmliL2dvLW9ic2VydmVyIgogIHBhY2thZ2VzID0gWyIuIl0KICByZXZp\nc2lvbiA9ICJhNTJmMjM0MjQ0OTI0NmQ1YmNjMjczZTY1Y2JkY2ZhNWY3ZDZj\nNjNjIgoKW1twcm9qZWN0c11dCiAgbmFtZSA9ICJnaXRodWIuY29tL29wZW50\ncmFjaW5nL29wZW50cmFjaW5nLWdvIgogIHBhY2thZ2VzID0gWwogICAgIi4i\nLAogICAgImV4dCIsCiAgICAibG9nIiwKICBdCiAgcmV2aXNpb24gPSAiMTk0\nOWRkYmZkMTQ3YWZkNGQ5NjRhOWYwMGIyNGViMjkxZTBlN2MzOCIKICB2ZXJz\naW9uID0gInYxLjAuMiIKCltbcHJvamVjdHNdXQogIG5hbWUgPSAiZ2l0aHVi\nLmNvbS9vcGVuemlwa2luL3ppcGtpbi1nby1vcGVudHJhY2luZyIKICBwYWNr\nYWdlcyA9IFsKICAgICIuIiwKICAgICJmbGFnIiwKICAgICJ0aHJpZnQvZ2Vu\nLWdvL3NjcmliZSIsCiAgICAidGhyaWZ0L2dlbi1nby96aXBraW5jb3JlIiwK\nICAgICJ0eXBlcyIsCiAgICAid2lyZSIsCiAgXQogIHJldmlzaW9uID0gIjQ1\nZTkwYjAwNzEwYTRjMzRhMWE3ZDhhNzhkOTBmOWIwMTBiMGJkNGQiCiAgdmVy\nc2lvbiA9ICJ2MC4zLjIiCgpbW3Byb2plY3RzXV0KICBicmFuY2ggPSAibWFz\ndGVyIgogIG5hbWUgPSAiZ2l0aHViLmNvbS9wZWxsZXRpZXIvZ28tdG9tbCIK\nICBwYWNrYWdlcyA9IFsiLiJdCiAgcmV2aXNpb24gPSAiMDViY2MwZmIwZDNl\nNjBkYTRiOGRkNWJkN2UwZWE1NjNlYjRjYTk0MyIKCltbcHJvamVjdHNdXQog\nIGJyYW5jaCA9ICJtYXN0ZXIiCiAgbmFtZSA9ICJnaXRodWIuY29tL3BldGFy\nL0dvTExSQiIKICBwYWNrYWdlcyA9IFsibGxyYiJdCiAgcmV2aXNpb24gPSAi\nNTNiZTBkMzZhODRjMmE4ODZjYTA1N2QzNGI2YWE0NDY4ZGY5Y2NiNCIKCltb\ncHJvamVjdHNdXQogIGJyYW5jaCA9ICJtYXN0ZXIiCiAgbmFtZSA9ICJnaXRo\ndWIuY29tL3BldGVybWF0dGlzL2dvaWQiCiAgcGFja2FnZXMgPSBbIi4iXQog\nIHJldmlzaW9uID0gImIwYjE2MTViNzhlNWVlNTk3Mzk1NDViYjM4NDI2Mzgz\nYjJjZGE0YzkiCgpbW3Byb2plY3RzXV0KICBuYW1lID0gImdpdGh1Yi5jb20v\ncGllcnJlYy9sejQiCiAgcGFja2FnZXMgPSBbIi4iXQogIHJldmlzaW9uID0g\nIjA4YzI3OTM5ZGYxYmQ5NWU4ODFlMmMyMzY3YTc0OTk2NGFkMWZjZWIiCiAg\ndmVyc2lvbiA9ICJ2MS4wLjEiCgpbW3Byb2plY3RzXV0KICBuYW1lID0gImdp\ndGh1Yi5jb20vcGllcnJlYy94eEhhc2giCiAgcGFja2FnZXMgPSBbInh4SGFz\naDMyIl0KICByZXZpc2lvbiA9ICJmMDUxYmI3ZjFkMWFhZjFiNWE2NjVkNzRm\nYjZiMDIxNzcxMmM2OWY3IgogIHZlcnNpb24gPSAidjAuMS4xIgoKW1twcm9q\nZWN0c11dCiAgbmFtZSA9ICJnaXRodWIuY29tL3BrZy9lcnJvcnMiCiAgcGFj\na2FnZXMgPSBbIi4iXQogIHJldmlzaW9uID0gIjY0NWVmMDA0NTllZDg0YTEx\nOTE5N2JmYjhkODIwNTA0MmM2ZGY2M2QiCiAgdmVyc2lvbiA9ICJ2MC44LjAi\nCgpbW3Byb2plY3RzXV0KICBuYW1lID0gImdpdGh1Yi5jb20vcG1lemFyZC9n\nby1kaWZmbGliIgogIHBhY2thZ2VzID0gWyJkaWZmbGliIl0KICByZXZpc2lv\nbiA9ICI3OTI3ODZjNzQwMGExMzYyODJjMTY2NDY2NWFlMGE4ZGI5MjFjNmMy\nIgogIHZlcnNpb24gPSAidjEuMC4wIgoKW1twcm9qZWN0c11dCiAgbmFtZSA9\nICJnaXRodWIuY29tL3Byb21ldGhldXMvY2xpZW50X2dvbGFuZyIKICBwYWNr\nYWdlcyA9IFsKICAgICJwcm9tZXRoZXVzIiwKICAgICJwcm9tZXRoZXVzL2dy\nYXBoaXRlIiwKICBdCiAgcmV2aXNpb24gPSAiOTY3Nzg5MDUwYmE5NGRlY2Ew\nNGE1ZTg0Y2NlOGFkNDcyY2UzMTNjMSIKICB2ZXJzaW9uID0gInYwLjkuMC1w\ncmUxIgoKW1twcm9qZWN0c11dCiAgYnJhbmNoID0gIm1hc3RlciIKICBuYW1l\nID0gImdpdGh1Yi5jb20vcHJvbWV0aGV1cy9jbGllbnRfbW9kZWwiCiAgcGFj\na2FnZXMgPSBbImdvIl0KICByZXZpc2lvbiA9ICI2ZjM4MDYwMTg2MTI5MzA5\nNDExMjdmMmE3YzZjNDUzYmEyYzUyN2QyIgoKW1twcm9qZWN0c11dCiAgYnJh\nbmNoID0gIm1hc3RlciIKICBuYW1lID0gImdpdGh1Yi5jb20vcHJvbWV0aGV1\ncy9jb21tb24iCiAgcGFja2FnZXMgPSBbCiAgICAiZXhwZm10IiwKICAgICJp\nbnRlcm5hbC9iaXRidWNrZXQub3JnL3d3L2dvYXV0b25lZyIsCiAgICAibW9k\nZWwiLAogIF0KICByZXZpc2lvbiA9ICIxYmFiNTVkZDA1ZGJmZjM4NDUyNGE2\nYTFjOTkwMDZkOWViNWYxMzliIgoKW1twcm9qZWN0c11dCiAgYnJhbmNoID0g\nIm1hc3RlciIKICBuYW1lID0gImdpdGh1Yi5jb20vcHJvbWV0aGV1cy9wcm9j\nZnMiCiAgcGFja2FnZXMgPSBbCiAgICAiLiIsCiAgICAiaW50ZXJuYWwvdXRp\nbCIsCiAgICAibmZzIiwKICAgICJ4ZnMiLAogIF0KICByZXZpc2lvbiA9ICI4\nYjFjMmRhMGQ1NmRlZmZkYmI5ZTQ4ZDQ0MTRiNGU2NzRiZDgwODNlIgoKW1tw\ncm9qZWN0c11dCiAgYnJhbmNoID0gIm1hc3RlciIKICBuYW1lID0gImdpdGh1\nYi5jb20vcmNyb3dsZXkvZ28tbWV0cmljcyIKICBwYWNrYWdlcyA9IFsKICAg\nICIuIiwKICAgICJleHAiLAogIF0KICByZXZpc2lvbiA9ICIxZjMwZmU5MDk0\nYTUxM2NlNGM3MDBiOWE1NDQ1OGJiYjBjOTY5OTZjIgoKW1twcm9qZWN0c11d\nCiAgYnJhbmNoID0gIm1hc3RlciIKICBuYW1lID0gImdpdGh1Yi5jb20vcnVi\neWlzdC9jaXJjdWl0YnJlYWtlciIKICBwYWNrYWdlcyA9IFsiLiJdCiAgcmV2\naXNpb24gPSAiMjA3NGFkYmE1ZGRjN2Q1Zjc1NTk0NDhhOWMzMDY2NTczNTIx\nYzViZiIKCltbcHJvamVjdHNdXQogIG5hbWUgPSAiZ2l0aHViLmNvbS9ydXNz\ncm9zcy9ibGFja2ZyaWRheSIKICBwYWNrYWdlcyA9IFsiLiJdCiAgcmV2aXNp\nb24gPSAiNDA0ODg3MmIxNmNjMGZjMmM1ZmQ5ZWFjZjBlZDJjMmZlZGFhMGM4\nYyIKICB2ZXJzaW9uID0gInYxLjUiCgpbW3Byb2plY3RzXV0KICBuYW1lID0g\nImdpdGh1Yi5jb20vc2FzaGEtcy9nby1kZWFkbG9jayIKICBwYWNrYWdlcyA9\nIFsiLiJdCiAgcmV2aXNpb24gPSAiMDNkNDBlNWRiZDU0ODg2NjdhMTNiM2My\nNjAwYjJmN2MyODg2ZjAyZiIKICB2ZXJzaW9uID0gInYwLjIuMCIKCltbcHJv\namVjdHNdXQogIG5hbWUgPSAiZ2l0aHViLmNvbS9zYXRvcmkvZ28udXVpZCIK\nICBwYWNrYWdlcyA9IFsiLiJdCiAgcmV2aXNpb24gPSAiZjU4NzY4Y2MxYTdh\nN2U3N2EzYmQ0OWU5OGNkZDIxNDE5Mzk5YjZhMyIKICB2ZXJzaW9uID0gInYx\nLjIuMCIKCltbcHJvamVjdHNdXQogIGJyYW5jaCA9ICJtYXN0ZXIiCiAgbmFt\nZSA9ICJnaXRodWIuY29tL3NkYm95ZXIvY29uc3RleHQiCiAgcGFja2FnZXMg\nPSBbIi4iXQogIHJldmlzaW9uID0gIjgzNmExNDQ1NzM1MzNlYTRkYTRlNjky\nOWMyMzVmZDM0OGFlZDFjODAiCgpbW3Byb2plY3RzXV0KICBuYW1lID0gImdp\ndGh1Yi5jb20vc2lydXBzZW4vbG9ncnVzIgogIHBhY2thZ2VzID0gWyIuIl0K\nICByZXZpc2lvbiA9ICJmMDA2YzJhYzQ3MTA4NTVjZjBmOTE2ZGQ2Yjc3YWNm\nNmIwNDhkYzZlIgogIHZlcnNpb24gPSAidjEuMC4zIgoKW1twcm9qZWN0c11d\nCiAgbmFtZSA9ICJnaXRodWIuY29tL3NwZjEzL2NvYnJhIgogIHBhY2thZ2Vz\nID0gWwogICAgIi4iLAogICAgImRvYyIsCiAgXQogIHJldmlzaW9uID0gIjdi\nMmM1YWM5ZmMwNGZjNWVmYWZiNjA3MDA3MTNkNGZhNjA5Yjc3N2IiCiAgdmVy\nc2lvbiA9ICJ2MC4wLjEiCgpbW3Byb2plY3RzXV0KICBuYW1lID0gImdpdGh1\nYi5jb20vc3BmMTMvcGZsYWciCiAgcGFja2FnZXMgPSBbIi4iXQogIHJldmlz\naW9uID0gImU1N2UzZWViMzNmNzk1MjA0YzFjYTM1ZjU2YzQ0ZjgzMjI3YzZl\nNjYiCiAgdmVyc2lvbiA9ICJ2MS4wLjAiCgpbW3Byb2plY3RzXV0KICBuYW1l\nID0gImdpdGh1Yi5jb20vc3RyZXRjaHIvdGVzdGlmeSIKICBwYWNrYWdlcyA9\nIFsKICAgICJhc3NlcnQiLAogICAgInJlcXVpcmUiLAogIF0KICByZXZpc2lv\nbiA9ICI2OTQ4M2I0YmQxNGY1ODQ1YjVhMWU1NWJjYTE5ZTk1NGU4MjdmMWQw\nIgogIHZlcnNpb24gPSAidjEuMS40IgoKW1twcm9qZWN0c11dCiAgYnJhbmNo\nID0gIm1hc3RlciIKICBuYW1lID0gImdpdGh1Yi5jb20vd2FkZXkvZ29jb3Zt\nZXJnZSIKICBwYWNrYWdlcyA9IFsiLiJdCiAgcmV2aXNpb24gPSAiYjViZmE1\nOWVjMGFkYzQyMDQ3NWY5N2Y4OWI1ODA0NWM3MjFkNzYxYyIKCltbcHJvamVj\ndHNdXQogIGJyYW5jaCA9ICJtYXN0ZXIiCiAgbmFtZSA9ICJnaXRodWIuY29t\nL3h3YjE5ODkvc3FscGFyc2VyIgogIHBhY2thZ2VzID0gWwogICAgIi4iLAog\nICAgImRlcGVuZGVuY3kvYnl0ZXMyIiwKICAgICJkZXBlbmRlbmN5L2hhY2si\nLAogICAgImRlcGVuZGVuY3kvcXVlcnlwYiIsCiAgICAiZGVwZW5kZW5jeS9z\ncWx0eXBlcyIsCiAgXQogIHJldmlzaW9uID0gIjZhZmY4NjE1YTMzZmFmMWYy\nNjBhM2E4ZjgwMDQ1NjUxMTUwNWQ1ZmQiCiAgc291cmNlID0gImh0dHBzOi8v\nZ2l0aHViLmNvbS9kdC9zcWxwYXJzZXIiCgpbW3Byb2plY3RzXV0KICBuYW1l\nID0gImdvLm9wZW5jZW5zdXMuaW8iCiAgcGFja2FnZXMgPSBbCiAgICAiZXhw\nb3J0ZXIvc3RhY2tkcml2ZXIvcHJvcGFnYXRpb24iLAogICAgImludGVybmFs\nIiwKICAgICJpbnRlcm5hbC90YWdlbmNvZGluZyIsCiAgICAicGx1Z2luL29j\naHR0cCIsCiAgICAicGx1Z2luL29jaHR0cC9wcm9wYWdhdGlvbi9iMyIsCiAg\nICAic3RhdHMiLAogICAgInN0YXRzL2ludGVybmFsIiwKICAgICJzdGF0cy92\naWV3IiwKICAgICJ0YWciLAogICAgInRyYWNlIiwKICAgICJ0cmFjZS9pbnRl\ncm5hbCIsCiAgICAidHJhY2UvcHJvcGFnYXRpb24iLAogIF0KICByZXZpc2lv\nbiA9ICIwMDk1YWVjNjZhZTE0ODAxYzY3MTEyMTBmNmYwNzE2NDExY2VmZGQz\nIgogIHZlcnNpb24gPSAidjAuOC4wIgoKW1twcm9qZWN0c11dCiAgYnJhbmNo\nID0gIm1hc3RlciIKICBuYW1lID0gImdvbGFuZy5vcmcveC9jcnlwdG8iCiAg\ncGFja2FnZXMgPSBbCiAgICAiYmNyeXB0IiwKICAgICJibG93ZmlzaCIsCiAg\nICAic3NoL3Rlcm1pbmFsIiwKICBdCiAgcmV2aXNpb24gPSAiYmQ2ZjI5OWZi\nMzgxZTRjMzM5M2QxYzRiMWYwYjk0ZjVlNzc2NTBjOCIKCltbcHJvamVjdHNd\nXQogIGJyYW5jaCA9ICJtYXN0ZXIiCiAgbmFtZSA9ICJnb2xhbmcub3JnL3gv\nbmV0IgogIHBhY2thZ2VzID0gWwogICAgImNvbnRleHQiLAogICAgImNvbnRl\neHQvY3R4aHR0cCIsCiAgICAiaHRtbCIsCiAgICAiaHRtbC9hdG9tIiwKICAg\nICJodHRwMiIsCiAgICAiaHR0cDIvaHBhY2siLAogICAgImlkbmEiLAogICAg\nImludGVybmFsL3RpbWVzZXJpZXMiLAogICAgImxleC9odHRwbGV4IiwKICAg\nICJwcm94eSIsCiAgICAidHJhY2UiLAogIF0KICByZXZpc2lvbiA9ICJjNzM2\nMjJjNzcyODAyNjYzMDUyNzNjYjU0NWY1NDUxNmNlZDk1YjkzIgoKW1twcm9q\nZWN0c11dCiAgYnJhbmNoID0gIm1hc3RlciIKICBuYW1lID0gImdvbGFuZy5v\ncmcveC9vYXV0aDIiCiAgcGFja2FnZXMgPSBbCiAgICAiLiIsCiAgICAiZ29v\nZ2xlIiwKICAgICJpbnRlcm5hbCIsCiAgICAiandzIiwKICAgICJqd3QiLAog\nIF0KICByZXZpc2lvbiA9ICI2ODgxZmVlNDEwYTVkYWY4NjM3MTM3MWY5YWQ0\nNTFiOTVlMTY4YjcxIgoKW1twcm9qZWN0c11dCiAgYnJhbmNoID0gIm1hc3Rl\nciIKICBuYW1lID0gImdvbGFuZy5vcmcveC9wZXJmIgogIHBhY2thZ2VzID0g\nWwogICAgImJlbmNoc3RhdCIsCiAgICAiY21kL2JlbmNoc3RhdCIsCiAgICAi\naW50ZXJuYWwvc3RhdHMiLAogICAgInN0b3JhZ2UiLAogICAgInN0b3JhZ2Uv\nYmVuY2hmbXQiLAogIF0KICByZXZpc2lvbiA9ICI0NDY5ZTZjZThjYzM5MjBm\nMWI0MjEyOGI5ZDU1N2JlYTJlMDg2MjFhIgoKW1twcm9qZWN0c11dCiAgYnJh\nbmNoID0gIm1hc3RlciIKICBuYW1lID0gImdvbGFuZy5vcmcveC9zeW5jIgog\nIHBhY2thZ2VzID0gWwogICAgImVycmdyb3VwIiwKICAgICJzeW5jbWFwIiwK\nICBdCiAgcmV2aXNpb24gPSAiOGUwYWE2ODhiNjU0ZWYyOGNhYTcyNTA2ZmE1\nZWM4ZGJhOWZjNzY5MCIKCltbcHJvamVjdHNdXQogIGJyYW5jaCA9ICJtYXN0\nZXIiCiAgbmFtZSA9ICJnb2xhbmcub3JnL3gvc3lzIgogIHBhY2thZ2VzID0g\nWwogICAgInVuaXgiLAogICAgIndpbmRvd3MiLAogIF0KICByZXZpc2lvbiA9\nICI5NWM2NTc2Mjk5MjU5ZGI5NjBmNmM1YjliNjllYTUyNDIyODYwZmNlIgoK\nW1twcm9qZWN0c11dCiAgbmFtZSA9ICJnb2xhbmcub3JnL3gvdGV4dCIKICBw\nYWNrYWdlcyA9IFsKICAgICJjb2xsYXRlIiwKICAgICJjb2xsYXRlL2J1aWxk\nIiwKICAgICJpbnRlcm5hbC9jb2xsdGFiIiwKICAgICJpbnRlcm5hbC9nZW4i\nLAogICAgImludGVybmFsL3RhZyIsCiAgICAiaW50ZXJuYWwvdHJpZWdlbiIs\nCiAgICAiaW50ZXJuYWwvdWNkIiwKICAgICJsYW5ndWFnZSIsCiAgICAic2Vj\ndXJlL2JpZGlydWxlIiwKICAgICJ0cmFuc2Zvcm0iLAogICAgInVuaWNvZGUv\nYmlkaSIsCiAgICAidW5pY29kZS9jbGRyIiwKICAgICJ1bmljb2RlL25vcm0i\nLAogICAgInVuaWNvZGUvcmFuZ2V0YWJsZSIsCiAgXQogIHJldmlzaW9uID0g\nIjQ3MGY0NWJmMjlmNDE0N2Q2ZmJkN2RmZDBhMDJhODQ4ZTQ5ZjViZjQiCgpb\nW3Byb2plY3RzXV0KICBicmFuY2ggPSAibWFzdGVyIgogIG5hbWUgPSAiZ29s\nYW5nLm9yZy94L3RpbWUiCiAgcGFja2FnZXMgPSBbInJhdGUiXQogIHJldmlz\naW9uID0gIjZkYzE3MzY4ZTA5YjBlODYzNGQ3MWNhYzgxNjhkODUzZTg2OWEw\nYzciCgpbW3Byb2plY3RzXV0KICBicmFuY2ggPSAibWFzdGVyIgogIG5hbWUg\nPSAiZ29sYW5nLm9yZy94L3Rvb2xzIgogIHBhY2thZ2VzID0gWwogICAgImNt\nZC9nb2ltcG9ydHMiLAogICAgImNtZC9nb3lhY2MiLAogICAgImNtZC9zdHJp\nbmdlciIsCiAgICAiY29udGFpbmVyL2ludHNldHMiLAogICAgImNvdmVyIiwK\nICAgICJnby9hc3QvYXN0dXRpbCIsCiAgICAiZ28vYnVpbGR1dGlsIiwKICAg\nICJnby9nY2V4cG9ydGRhdGEiLAogICAgImdvL2djaW1wb3J0ZXIxNSIsCiAg\nICAiZ28vbG9hZGVyIiwKICAgICJnby90eXBlcy90eXBldXRpbCIsCiAgICAi\naW1wb3J0cyIsCiAgXQogIHJldmlzaW9uID0gIjkwYjgwN2FkYTRjYzdhYjVk\nMTQwOWQ2MzdiMWYzYmViNWE3NzZiZTciCgpbW3Byb2plY3RzXV0KICBicmFu\nY2ggPSAibWFzdGVyIgogIG5hbWUgPSAiZ29vZ2xlLmdvbGFuZy5vcmcvYXBp\nIgogIHBhY2thZ2VzID0gWwogICAgImdlbnN1cHBvcnQiLAogICAgImdvb2ds\nZWFwaSIsCiAgICAiZ29vZ2xlYXBpL2ludGVybmFsL3VyaXRlbXBsYXRlcyIs\nCiAgICAiZ29vZ2xlYXBpL3RyYW5zcG9ydCIsCiAgICAiaW50ZXJuYWwiLAog\nICAgIml0ZXJhdG9yIiwKICAgICJvcHRpb24iLAogICAgInN0b3JhZ2UvdjEi\nLAogICAgInRyYW5zcG9ydC9odHRwIiwKICBdCiAgcmV2aXNpb24gPSAiOWM3\nOWRlZWJmNzQ5NmUzNTVkN2U5NWQ4MmQ0YWYxZmU0ZTc2OWIyZiIKCltbcHJv\namVjdHNdXQogIG5hbWUgPSAiZ29vZ2xlLmdvbGFuZy5vcmcvYXBwZW5naW5l\nIgogIHBhY2thZ2VzID0gWwogICAgIi4iLAogICAgImludGVybmFsIiwKICAg\nICJpbnRlcm5hbC9hcHBfaWRlbnRpdHkiLAogICAgImludGVybmFsL2Jhc2Ui\nLAogICAgImludGVybmFsL2RhdGFzdG9yZSIsCiAgICAiaW50ZXJuYWwvbG9n\nIiwKICAgICJpbnRlcm5hbC9tb2R1bGVzIiwKICAgICJpbnRlcm5hbC9yZW1v\ndGVfYXBpIiwKICAgICJpbnRlcm5hbC91cmxmZXRjaCIsCiAgICAidXJsZmV0\nY2giLAogIF0KICByZXZpc2lvbiA9ICIxNTBkYzU3YTFiNDMzZTY0MTU0MzAy\nYmRjNDBiNmJiOGFlZmEzMTNhIgogIHZlcnNpb24gPSAidjEuMC4wIgoKW1tw\ncm9qZWN0c11dCiAgYnJhbmNoID0gIm1hc3RlciIKICBuYW1lID0gImdvb2ds\nZS5nb2xhbmcub3JnL2dlbnByb3RvIgogIHBhY2thZ2VzID0gWwogICAgImdv\nb2dsZWFwaXMvYXBpL2Fubm90YXRpb25zIiwKICAgICJnb29nbGVhcGlzL2lh\nbS92MSIsCiAgICAiZ29vZ2xlYXBpcy9ycGMvY29kZSIsCiAgICAiZ29vZ2xl\nYXBpcy9ycGMvc3RhdHVzIiwKICBdCiAgcmV2aXNpb24gPSAiZjY3NmUwZjNh\nYzYzOTVmZjFhNTI5YWU1OWE2NjcwODc4YTgzNzFhNiIKCltbcHJvamVjdHNd\nXQogIGJyYW5jaCA9ICJtYXN0ZXIiCiAgbmFtZSA9ICJnb29nbGUuZ29sYW5n\nLm9yZy9ncnBjIgogIHBhY2thZ2VzID0gWwogICAgIi4iLAogICAgImJhbGFu\nY2VyIiwKICAgICJiYWxhbmNlci9iYXNlIiwKICAgICJiYWxhbmNlci9yb3Vu\nZHJvYmluIiwKICAgICJjaGFubmVseiIsCiAgICAiY29kZXMiLAogICAgImNv\nbm5lY3Rpdml0eSIsCiAgICAiY3JlZGVudGlhbHMiLAogICAgImVuY29kaW5n\nIiwKICAgICJlbmNvZGluZy9wcm90byIsCiAgICAiZ3JwY2xiL2dycGNfbGJf\ndjEvbWVzc2FnZXMiLAogICAgImdycGNsb2ciLAogICAgImhlYWx0aC9ncnBj\nX2hlYWx0aF92MSIsCiAgICAiaW50ZXJuYWwiLAogICAgImtlZXBhbGl2ZSIs\nCiAgICAibWV0YWRhdGEiLAogICAgIm5hbWluZyIsCiAgICAicGVlciIsCiAg\nICAicmVzb2x2ZXIiLAogICAgInJlc29sdmVyL2RucyIsCiAgICAicmVzb2x2\nZXIvcGFzc3Rocm91Z2giLAogICAgInN0YXRzIiwKICAgICJzdGF0dXMiLAog\nICAgInRhcCIsCiAgICAidHJhbnNwb3J0IiwKICBdCiAgcmV2aXNpb24gPSAi\nOGYwNmY4MmNhMzk0YjFhYzgzN2Q0YjBjMGNmYTA3MTg4YjBlOWRlZSIKCltb\ncHJvamVjdHNdXQogIGJyYW5jaCA9ICJ2MiIKICBuYW1lID0gImdvcGtnLmlu\nL3lhbWwudjIiCiAgcGFja2FnZXMgPSBbIi4iXQogIHJldmlzaW9uID0gIjI4\nN2NmMDg1NDZhYjVlN2UzN2Q1NWE4NGY3ZWQzZmQxZGIwMzZkZTUiCgpbW3By\nb2plY3RzXV0KICBuYW1lID0gImhvbm5lZi5jby9nby90b29scyIKICBwYWNr\nYWdlcyA9IFsKICAgICJjYWxsZ3JhcGgiLAogICAgImNhbGxncmFwaC9zdGF0\naWMiLAogICAgImRlcHJlY2F0ZWQiLAogICAgImZ1bmN0aW9ucyIsCiAgICAi\naW50ZXJuYWwvc2hhcmVkY2hlY2siLAogICAgImxpbnQiLAogICAgInNpbXBs\nZSIsCiAgICAic3NhIiwKICAgICJzc2Evc3NhdXRpbCIsCiAgICAic3RhdGlj\nY2hlY2siLAogICAgInN0YXRpY2NoZWNrL3ZycCIsCiAgICAidW51c2VkIiwK\nICBdCiAgcmV2aXNpb24gPSAiZDczYWI5OGU3YzM5ZmRjZjliYTY1MDYyZTQz\nZDM0MzEwZjE5ODM1MyIKICB2ZXJzaW9uID0gIjIwMTcuMi4yIgoKW3NvbHZl\nLW1ldGFdCiAgYW5hbHl6ZXItbmFtZSA9ICJkZXAiCiAgYW5hbHl6ZXItdmVy\nc2lvbiA9IDEKICBpbnB1dHMtZGlnZXN0ID0gIjgwMjI4MzYyMjMwNTlhZWFk\nODdmNmQ3ZDZiOTQ1MWE1ZDdhZDhhMjBmNDM1OGNiNzA5Y2NmMjg1OWY5Yzc0\nY2UiCiAgc29sdmVyLW5hbWUgPSAiZ3BzLWNkY2wiCiAgc29sdmVyLXZlcnNp\nb24gPSAxCg==\n", + "encoding": "base64", + "_links": { + "self": "https://api.github.com/repos/cockroachdb/cockroach/contents/Gopkg.lock?ref=master", + "git": "https://api.github.com/repos/cockroachdb/cockroach/git/blobs/816ac07074ab08023f4fea593888b1f87dd774b3", + "html": "https://github.com/cockroachdb/cockroach/blob/master/Gopkg.lock" + } +} diff --git a/spec/fixtures/github/contents_gopkg_toml.json b/spec/fixtures/github/contents_gopkg_toml.json new file mode 100644 index 00000000000..1b8320e184d --- /dev/null +++ b/spec/fixtures/github/contents_gopkg_toml.json @@ -0,0 +1,18 @@ +{ + "name": "Gopkg.toml", + "path": "Gopkg.toml", + "sha": "ad9e923036970af596b854534149b41bdc0ecfa1", + "size": 3254, + "url": "https://api.github.com/repos/cockroachdb/cockroach/contents/Gopkg.toml?ref=master", + "html_url": "https://github.com/cockroachdb/cockroach/blob/master/Gopkg.toml", + "git_url": "https://api.github.com/repos/cockroachdb/cockroach/git/blobs/ad9e923036970af596b854534149b41bdc0ecfa1", + "download_url": "https://raw.githubusercontent.com/cockroachdb/cockroach/master/Gopkg.toml", + "type": "file", + "content": "cmVxdWlyZWQgPSBbCiAiZ2l0aHViLmNvbS9jbGllbnQ5L21pc3NwZWxsL2Nt\nZC9taXNzcGVsbCIsCiAiZ2l0aHViLmNvbS9jb2Nrcm9hY2hkYi9jcmxmbXQi\nLAogImdpdGh1Yi5jb20vY29ja3JvYWNoZGIvc3RyZXNzIiwKICJnaXRodWIu\nY29tL2dvbGFuZy9kZXAvY21kL2RlcCIsCiAiZ2l0aHViLmNvbS9nb2xhbmcv\nbGludC9nb2xpbnQiLAogImdpdGh1Yi5jb20vZ29vZ2xlL3Bwcm9mIiwKICJn\naXRodWIuY29tL2dycGMtZWNvc3lzdGVtL2dycGMtZ2F0ZXdheS9wcm90b2Mt\nZ2VuLWdycGMtZ2F0ZXdheSIsCiAiZ2l0aHViLmNvbS9qdGVldXdlbi9nby1i\naW5kYXRhL2dvLWJpbmRhdGEiLAogImdpdGh1Yi5jb20va2lzaWVsay9lcnJj\naGVjayIsCiAiZ2l0aHViLmNvbS9tYXR0bi9nb3ZlcmFsbHMiLAogImdpdGh1\nYi5jb20vbWliay9kdXBsIiwKICJnaXRodWIuY29tL3dhZGV5L2dvY292bWVy\nZ2UiLAogImdvbGFuZy5vcmcveC9wZXJmL2NtZC9iZW5jaHN0YXQiLAogImdv\nbGFuZy5vcmcveC90b29scy9jbWQvZ29pbXBvcnRzIiwKICJnb2xhbmcub3Jn\nL3gvdG9vbHMvY21kL2dveWFjYyIsCiAiZ29sYW5nLm9yZy94L3Rvb2xzL2Nt\nZC9zdHJpbmdlciIsCl0KCmlnbm9yZWQgPSBbCiAgIyBOb24tZXhpc3RlbnQg\ncGFja2FnZSB1c2VkIGJ5IGEgdG95IHByb2dyYW0gaW4gYy1kZXBzL3Byb3Rv\nYnVmLgogICJnaXRodWIuY29tL2dvb2dsZS9wcm90b2J1Zi9leGFtcGxlcy90\ndXRvcmlhbCIsCl0KCiMgVGhlIGNvbGxhdGlvbiB0YWJsZXMgbXVzdCBuZXZl\nciBjaGFuZ2UuCltbY29uc3RyYWludF1dCiAgbmFtZSA9ICJnb2xhbmcub3Jn\nL3gvdGV4dCIKICByZXZpc2lvbiA9ICI0NzBmNDViZjI5ZjQxNDdkNmZiZDdk\nZmQwYTAyYTg0OGU0OWY1YmY0IgoKIyBodHRwczovL2dpdGh1Yi5jb20vY29y\nZW9zL2V0Y2QvY29tbWl0L2YwM2VkMzMKIwojIGh0dHBzOi8vZ2l0aHViLmNv\nbS9jb3Jlb3MvZXRjZC9jb21taXQvY2UwYWQzNzcKW1tjb25zdHJhaW50XV0K\nICBuYW1lID0gImdpdGh1Yi5jb20vY29yZW9zL2V0Y2QiCiAgYnJhbmNoID0g\nIm1hc3RlciIKCiMgVXNlZCBmb3IgdGhlIEFQSSBjbGllbnQ7IHdlIHdhbnQg\ndGhlIGxhdGVzdC4KW1tjb25zdHJhaW50XV0KICBuYW1lID0gImdpdGh1Yi5j\nb20vZG9ja2VyL2RvY2tlciIKICBicmFuY2ggPSAibWFzdGVyIgoKIyBodHRw\nczovL2dpdGh1Yi5jb20vZ2V0c2VudHJ5L3JhdmVuLWdvL3B1bGwvMTM5Cltb\nY29uc3RyYWludF1dCiAgbmFtZSA9ICJnaXRodWIuY29tL2dldHNlbnRyeS9y\nYXZlbi1nbyIKICBzb3VyY2UgPSAiaHR0cHM6Ly9naXRodWIuY29tL2NvY2ty\nb2FjaGRiL3JhdmVuLWdvIgoKIyBVc2VkIGZvciBiZW5jaG1hcmtzLCBzaG91\nbGQgYmUgcmVjZW50LgpbW2NvbnN0cmFpbnRdXQogIG5hbWUgPSAiZ2l0aHVi\nLmNvbS9nby1zcWwtZHJpdmVyL215c3FsIgogIGJyYW5jaCA9ICJtYXN0ZXIi\nCgojIGh0dHBzOi8vZ2l0aHViLmNvbS9qdGVldXdlbi9nby1iaW5kYXRhL3B1\nbGwvMTU4CltbY29uc3RyYWludF1dCiAgbmFtZSA9ICJnaXRodWIuY29tL2p0\nZWV1d2VuL2dvLWJpbmRhdGEiCiAgc291cmNlID0gImh0dHBzOi8vZ2l0aHVi\nLmNvbS9kaW0xMy9nby1iaW5kYXRhIgogIGJyYW5jaCA9ICJtYXN0ZXIiCgoj\nIGh0dHBzOi8vZ2l0aHViLmNvbS9tb250YW5hZmx5bm4vc3RhdHMvcmVsZWFz\nZXMgKGxhdGVzdCBpcyAyMDE1LTEwLTE0KQpbW2NvbnN0cmFpbnRdXQogIG5h\nbWUgPSAiZ2l0aHViLmNvbS9tb250YW5hZmx5bm4vc3RhdHMiCiAgYnJhbmNo\nID0gIm1hc3RlciIKCiMgaHR0cHM6Ly9naXRodWIuY29tL3J1Ynlpc3QvY2ly\nY3VpdGJyZWFrZXIvY29tbWl0L2FmOTU4MzAKW1tjb25zdHJhaW50XV0KICBu\nYW1lID0gImdpdGh1Yi5jb20vcnVieWlzdC9jaXJjdWl0YnJlYWtlciIKICBi\ncmFuY2ggPSAibWFzdGVyIgoKIyBodHRwczovL2dpdGh1Yi5jb20veHdiMTk4\nOS9zcWxwYXJzZXIvaXNzdWVzLzMxCiMgaHR0cHM6Ly9naXRodWIuY29tL3h3\nYjE5ODkvc3FscGFyc2VyL2lzc3Vlcy8zMgpbW2NvbnN0cmFpbnRdXQogIG5h\nbWUgPSAiZ2l0aHViLmNvbS94d2IxOTg5L3NxbHBhcnNlciIKICBzb3VyY2Ug\nPSAiaHR0cHM6Ly9naXRodWIuY29tL2R0L3NxbHBhcnNlciIKCiMgVGhlIG1h\nc3RlciB2ZXJzaW9uIG9mIGdvLnV1aWQgaGFzIGFuIGluY29tcGF0aWJsZSBp\nbnRlcmZhY2UgYW5kIChhcwojIG9mIDIwMTgtMDYtMDYpIGEgc2VyaW91cyBi\ndWcuIERvbid0IHVwZ3JhZGUgd2l0aG91dCBtYWtpbmcgc3VyZQojIHRoYXQg\nYnVnIGlzIGZpeGVkLgojIGh0dHBzOi8vZ2l0aHViLmNvbS9jb2Nrcm9hY2hk\nYi9jb2Nrcm9hY2gvaXNzdWVzLzI2MzMyCltbY29uc3RyYWludF1dCiAgbmFt\nZSA9ICJnaXRodWIuY29tL3NhdG9yaS9nby51dWlkIgogIHZlcnNpb24gPSAi\ndjEuMi4wIgoKIyBnaXRodWIuY29tL2RvY2tlci9kb2NrZXIgZGVwZW5kcyBv\nbiBhIGZldyBmdW5jdGlvbnMgbm90IGluY2x1ZGVkIGluIHRoZQojIGxhdGVz\ndCByZWxlYXNlOiByZWZlcmVuY2Uue0ZhbWlsaWFyTmFtZSxQYXJzZU5vcm1h\nbGl6ZWROYW1lZCxUYWdOYW1lT25seX0uCiMKIyBodHRwczovL2dpdGh1Yi5j\nb20vZG9ja2VyL2Rpc3RyaWJ1dGlvbi9jb21taXQvNDI5Yzc1ZgojIGh0dHBz\nOi8vZ2l0aHViLmNvbS9kb2NrZXIvZGlzdHJpYnV0aW9uL2NvbW1pdC8yY2Fl\nYjYxCltbb3ZlcnJpZGVdXQogIG5hbWUgPSAiZ2l0aHViLmNvbS9kb2NrZXIv\nZGlzdHJpYnV0aW9uIgogIGJyYW5jaCA9ICJtYXN0ZXIiCgpbcHJ1bmVdCiAg\nZ28tdGVzdHMgPSB0cnVlCiAgdW51c2VkLXBhY2thZ2VzID0gdHJ1ZQoKICAj\nIEF2b2lkIHBydW5pbmcgcHJvamVjdHMgY29udGFpbmluZyBuZWVkZWQgcHJv\ndG9zLgoKICBbW3BydW5lLnByb2plY3RdXQogICAgbmFtZSA9ICJnaXRodWIu\nY29tL2dvZ28vcHJvdG9idWYiCiAgICB1bnVzZWQtcGFja2FnZXMgPSBmYWxz\nZQoKICBbW3BydW5lLnByb2plY3RdXQogICAgbmFtZSA9ICJnaXRodWIuY29t\nL2dycGMtZWNvc3lzdGVtL2dycGMtZ2F0ZXdheSIKICAgIHVudXNlZC1wYWNr\nYWdlcyA9IGZhbHNlCgogIFtbcHJ1bmUucHJvamVjdF1dCiAgICBuYW1lID0g\nImdpdGh1Yi5jb20vcHJvbWV0aGV1cy9jbGllbnRfbW9kZWwiCiAgICB1bnVz\nZWQtcGFja2FnZXMgPSBmYWxzZQoKICAjIENvbnRhaW5zIHBhY2thZ2VzIHdp\ndGggdXNlZCBDIGZpbGVzLgogIFtbcHJ1bmUucHJvamVjdF1dCiAgICBuYW1l\nID0gImdpdGh1Yi5jb20va256L2dvLWxpYmVkaXQiCiAgICB1bnVzZWQtcGFj\na2FnZXMgPSBmYWxzZQo=\n", + "encoding": "base64", + "_links": { + "self": "https://api.github.com/repos/cockroachdb/cockroach/contents/Gopkg.toml?ref=master", + "git": "https://api.github.com/repos/cockroachdb/cockroach/git/blobs/ad9e923036970af596b854534149b41bdc0ecfa1", + "html": "https://github.com/cockroachdb/cockroach/blob/master/Gopkg.toml" + } +} From f3163953d0623ce6d21f8a56240ff35494e912ca Mon Sep 17 00:00:00 2001 From: Grey Baker Date: Thu, 19 Jul 2018 10:26:32 +0100 Subject: [PATCH 03/28] Dep: Make FileParser work (although currently very basic) --- lib/dependabot/file_parsers/go/dep.rb | 112 +- spec/dependabot/file_parsers/go/dep_spec.rb | 90 ++ spec/fixtures/go/gopkg_locks/cockroach.lock | 1309 +++++++++++++++++++ spec/fixtures/go/gopkg_locks/fider.lock | 136 ++ spec/fixtures/go/gopkg_locks/flagr.lock | 434 ++++++ spec/fixtures/go/gopkg_tomls/cockroach.toml | 112 ++ spec/fixtures/go/gopkg_tomls/fider.toml | 32 + spec/fixtures/go/gopkg_tomls/flagr.toml | 154 +++ 8 files changed, 2361 insertions(+), 18 deletions(-) create mode 100644 spec/fixtures/go/gopkg_locks/cockroach.lock create mode 100644 spec/fixtures/go/gopkg_locks/fider.lock create mode 100644 spec/fixtures/go/gopkg_locks/flagr.lock create mode 100644 spec/fixtures/go/gopkg_tomls/cockroach.toml create mode 100644 spec/fixtures/go/gopkg_tomls/fider.toml create mode 100644 spec/fixtures/go/gopkg_tomls/flagr.toml diff --git a/lib/dependabot/file_parsers/go/dep.rb b/lib/dependabot/file_parsers/go/dep.rb index 3f0f53df874..9040864c77a 100644 --- a/lib/dependabot/file_parsers/go/dep.rb +++ b/lib/dependabot/file_parsers/go/dep.rb @@ -1,35 +1,111 @@ # frozen_string_literal: true +require "toml-rb" + +require "dependabot/errors" require "dependabot/dependency" require "dependabot/file_parsers/base" +# Relevant dep docs can be found at: +# - https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md +# - https://github.com/golang/dep/blob/master/docs/Gopkg.lock.md module Dependabot module FileParsers module Go class Dep < Dependabot::FileParsers::Base + require "dependabot/file_parsers/base/dependency_set" + + REQUIREMENT_TYPES = %w(constraint override).freeze + def parse - # Parse the dependency file and return a Dependabot::Dependency - # object for each dependency. - # If possible, this should be done in Ruby (since it's easier to - # maintain). However, if we need to parse a lockfile that has a - # non-standard format we can shell out to a helper in a language of - # our choice (see JavaScript example where we parse the yarn.lock). - [ - Dependency.new( - name: "my_dependency", - version: "1.0.1", + dependency_set = DependencySet.new + dependency_set += manifest_dependencies + dependency_set += lockfile_dependencies + dependency_set.dependencies + end + + private + + def manifest_dependencies + dependency_set = DependencySet.new + + REQUIREMENT_TYPES.each do |type| + parsed_file(manifest).fetch(type, {}).each do |details| + dependency_set << Dependency.new( + name: details.fetch("name"), + version: nil, + package_manager: "dep", + requirements: [{ + requirement: requirement_from_declaration(details), + file: manifest.name, + groups: [], + source: source_from_declaration(details) + }] + ) + end + end + + dependency_set + end + + def lockfile_dependencies + dependency_set = DependencySet.new + + parsed_file(lockfile).fetch("projects", {}).each do |details| + dependency_set << Dependency.new( + name: details.fetch("name"), + version: version_from_lockfile(details), package_manager: "dep", - requirements: [{ - requirement: ">= 1.0.0", - file: "Gopkg.toml", - groups: [], - source: nil - }] + requirements: [] ) - ] + end + + dependency_set end - private + def version_from_lockfile(details) + details["version"]&.sub(/^v?/, "") || details.fetch("revision") + end + + def requirement_from_declaration(declaration) + unless declaration.is_a?(Hash) + raise "Unexpected dependency declaration: #{declaration}" + end + + declaration["version"] + end + + def source_from_declaration(declaration) + unless declaration.is_a?(Hash) + raise "Unexpected dependency declaration: #{declaration}" + end + + # TODO: Figure out git sources (particularly ones which don't have + # a version) at this point, so we can use GitCommitChecker later + # Most likely looking docs: + # https://github.com/golang/dep/blob/master/vendor/github.com/Masterminds/vcs/vcs_remote_lookup.go + { + type: "default", + source: declaration["source"] || declaration["name"], + branch: declaration["branch"], + ref: declaration["revision"] + } + end + + def parsed_file(file) + @parsed_file ||= {} + @parsed_file[file.name] ||= TomlRB.parse(file.content) + rescue TomlRB::ParseError + raise Dependabot::DependencyFileNotParseable, file.path + end + + def manifest + @manifest ||= get_original_file("Gopkg.toml") + end + + def lockfile + @lockfile ||= get_original_file("Gopkg.lock") + end def check_required_files %w(Gopkg.toml Gopkg.lock).each do |filename| diff --git a/spec/dependabot/file_parsers/go/dep_spec.rb b/spec/dependabot/file_parsers/go/dep_spec.rb index 2f6587271e3..b65fde3c4c6 100644 --- a/spec/dependabot/file_parsers/go/dep_spec.rb +++ b/spec/dependabot/file_parsers/go/dep_spec.rb @@ -1,8 +1,98 @@ # frozen_string_literal: true +require "dependabot/dependency_file" +require "dependabot/source" require "dependabot/file_parsers/go/dep" require_relative "../shared_examples_for_file_parsers" RSpec.describe Dependabot::FileParsers::Go::Dep do it_behaves_like "a dependency file parser" + + let(:parser) { described_class.new(dependency_files: files, source: source) } + + let(:files) { [manifest, lockfile] } + let(:manifest) do + Dependabot::DependencyFile.new( + name: "Gopkg.toml", + content: fixture("go", "gopkg_tomls", manifest_fixture_name) + ) + end + let(:lockfile) do + Dependabot::DependencyFile.new( + name: "Gopkg.lock", + content: fixture("go", "gopkg_locks", lockfile_fixture_name) + ) + end + let(:manifest_fixture_name) { "cockroach.toml" } + let(:lockfile_fixture_name) { "cockroach.lock" } + let(:source) do + Dependabot::Source.new( + provider: "github", + repo: "gocardless/bump", + directory: "/" + ) + end + + describe "parse" do + subject(:dependencies) { parser.parse } + + its(:length) { is_expected.to eq(149) } + + describe "top level dependencies" do + subject(:dependencies) { parser.parse.select(&:top_level?) } + + its(:length) { is_expected.to eq(11) } + + describe "a regular dependency dependency" do + subject(:dependency) do + dependencies.find { |d| d.name == "github.com/satori/go.uuid" } + end + + it "has the right details" do + expect(dependency).to be_a(Dependabot::Dependency) + expect(dependency.name).to eq("github.com/satori/go.uuid") + expect(dependency.version).to eq("1.2.0") + expect(dependency.requirements).to eq( + [{ + requirement: "v1.2.0", + file: "Gopkg.toml", + groups: [], + source: { + type: "default", + source: "github.com/satori/go.uuid", + branch: nil, + ref: nil + } + }] + ) + end + end + + describe "a git dependency dependency" do + subject(:dependency) do + dependencies.find { |d| d.name == "golang.org/x/text" } + end + + it "has the right details" do + expect(dependency).to be_a(Dependabot::Dependency) + expect(dependency.name).to eq("golang.org/x/text") + expect(dependency.version). + to eq("470f45bf29f4147d6fbd7dfd0a02a848e49f5bf4") + expect(dependency.requirements).to eq( + [{ + requirement: nil, + file: "Gopkg.toml", + groups: [], + source: { + type: "default", + source: "golang.org/x/text", + branch: nil, + ref: "470f45bf29f4147d6fbd7dfd0a02a848e49f5bf4" + } + }] + ) + end + end + end + end end diff --git a/spec/fixtures/go/gopkg_locks/cockroach.lock b/spec/fixtures/go/gopkg_locks/cockroach.lock new file mode 100644 index 00000000000..816ac07074a --- /dev/null +++ b/spec/fixtures/go/gopkg_locks/cockroach.lock @@ -0,0 +1,1309 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + name = "cloud.google.com/go" + packages = [ + "compute/metadata", + "iam", + "internal", + "internal/optional", + "internal/trace", + "internal/version", + "storage", + ] + revision = "0fd7230b2a7505833d5f69b75cbd6c9582401479" + version = "v0.23.0" + +[[projects]] + name = "github.com/Azure/azure-sdk-for-go" + packages = [ + "storage", + "version", + ] + revision = "e67cd39e942c417ae5e9ae1165f778d9fe8996e0" + version = "v14.5.0" + +[[projects]] + branch = "master" + name = "github.com/Azure/go-ansiterm" + packages = [ + ".", + "winterm", + ] + revision = "d6e3b3328b783f23731bc4d058875b0371ff8109" + +[[projects]] + name = "github.com/Azure/go-autorest" + packages = [ + "autorest", + "autorest/adal", + "autorest/azure", + "autorest/date", + ] + revision = "0ae36a9e544696de46fdadb7b0d5fb38af48c063" + version = "v10.2.0" + +[[projects]] + branch = "parse-constraints-with-dash-in-pre" + name = "github.com/Masterminds/semver" + packages = ["."] + revision = "a93e51b5a57ef416dac8bb02d11407b6f55d8929" + source = "https://github.com/carolynvs/semver.git" + +[[projects]] + name = "github.com/Masterminds/vcs" + packages = ["."] + revision = "6f1c6d150500e452704e9863f68c2559f58616bf" + version = "v1.12.0" + +[[projects]] + branch = "master" + name = "github.com/MichaelTJones/walk" + packages = ["."] + revision = "4748e29d5718c2df4028a6543edf86fd8cc0f881" + +[[projects]] + name = "github.com/Microsoft/go-winio" + packages = ["."] + revision = "78439966b38d69bf38227fbf57ac8a6fee70f69a" + version = "v0.4.5" + +[[projects]] + branch = "master" + name = "github.com/Nvveen/Gotty" + packages = ["."] + revision = "cd527374f1e5bff4938207604a14f2e38a9cf512" + +[[projects]] + name = "github.com/PuerkitoBio/goquery" + packages = ["."] + revision = "e1271ee34c6a305e38566ecd27ae374944907ee9" + version = "v1.1.0" + +[[projects]] + name = "github.com/Shopify/sarama" + packages = ["."] + revision = "bbdbe644099b7fdc8327d5cc69c030945188b2e9" + version = "v1.13.0" + +[[projects]] + branch = "master" + name = "github.com/StackExchange/wmi" + packages = ["."] + revision = "ea383cf3ba6ec950874b8486cd72356d007c768f" + +[[projects]] + name = "github.com/VividCortex/ewma" + packages = ["."] + revision = "b24eb346a94c3ba12c1da1e564dbac1b498a77ce" + version = "v1.1.1" + +[[projects]] + branch = "master" + name = "github.com/abourget/teamcity" + packages = ["."] + revision = "e241104394f91bf4e65048f9ea5d0c0f3c25b35e" + +[[projects]] + branch = "master" + name = "github.com/andy-kimball/arenaskl" + packages = ["."] + revision = "224761e552afe64db9d93004f8d5d3a686b89771" + +[[projects]] + branch = "master" + name = "github.com/andybalholm/cascadia" + packages = ["."] + revision = "349dd0209470eabd9514242c688c403c0926d266" + +[[projects]] + name = "github.com/apache/thrift" + packages = ["lib/go/thrift"] + revision = "327ebb6c2b6df8bf075da02ef45a2a034e9b79ba" + version = "0.11.0" + +[[projects]] + branch = "master" + name = "github.com/armon/go-radix" + packages = ["."] + revision = "1fca145dffbcaa8fe914309b1ec0cfc67500fe61" + +[[projects]] + name = "github.com/aws/aws-sdk-go" + packages = [ + "aws", + "aws/awserr", + "aws/awsutil", + "aws/client", + "aws/client/metadata", + "aws/corehandlers", + "aws/credentials", + "aws/credentials/ec2rolecreds", + "aws/credentials/endpointcreds", + "aws/credentials/stscreds", + "aws/defaults", + "aws/ec2metadata", + "aws/endpoints", + "aws/request", + "aws/session", + "aws/signer/v4", + "internal/shareddefaults", + "private/protocol", + "private/protocol/query", + "private/protocol/query/queryutil", + "private/protocol/rest", + "private/protocol/restxml", + "private/protocol/xml/xmlutil", + "service/s3", + "service/s3/s3iface", + "service/s3/s3manager", + "service/sts", + ] + revision = "ee1f179877b2daf2aaabf71fa900773bf8842253" + version = "v1.12.19" + +[[projects]] + branch = "master" + name = "github.com/axiomhq/hyperloglog" + packages = ["."] + revision = "baba800be098d9f4303352fa6214a933e371e3da" + +[[projects]] + branch = "master" + name = "github.com/backtrace-labs/go-bcd" + packages = ["."] + revision = "5d8e01b2f0438922289238fd3ba043761daf1102" + +[[projects]] + branch = "master" + name = "github.com/benesch/cgosymbolizer" + packages = ["."] + revision = "70e1ee2b39d3b616a6ab9996820dde224c27f351" + +[[projects]] + branch = "master" + name = "github.com/beorn7/perks" + packages = ["quantile"] + revision = "3a771d992973f24aa725d07868b467d1ddfceafb" + +[[projects]] + branch = "master" + name = "github.com/biogo/store" + packages = ["llrb"] + revision = "913427a1d5e89604e50ea1db0f28f34966d61602" + +[[projects]] + name = "github.com/boltdb/bolt" + packages = ["."] + revision = "2f1ce7a837dcb8da3ec595b1dac9d0632f0f99e8" + version = "v1.3.1" + +[[projects]] + name = "github.com/cenk/backoff" + packages = ["."] + revision = "61153c768f31ee5f130071d08fc82b85208528de" + version = "v1.1.0" + +[[projects]] + name = "github.com/certifi/gocertifi" + packages = ["."] + revision = "3fd9e1adb12b72d2f3f82191d49be9b93c69f67c" + version = "2017.07.27" + +[[projects]] + name = "github.com/client9/misspell" + packages = [ + ".", + "cmd/misspell", + ] + revision = "59894abde931a32630d4e884a09c682ed20c5c7c" + version = "v0.3.0" + +[[projects]] + name = "github.com/cockroachdb/apd" + packages = ["."] + revision = "b1ce49cb2a474f4416531e7395373eaafaa4fbe2" + version = "v1.0.0" + +[[projects]] + branch = "master" + name = "github.com/cockroachdb/cmux" + packages = ["."] + revision = "30d10be492927e2dcae0089c374c455d42414fcb" + +[[projects]] + branch = "master" + name = "github.com/cockroachdb/cockroach-go" + packages = ["crdb"] + revision = "59c0560478b705bf9bd12f9252224a0fad7c87df" + +[[projects]] + branch = "master" + name = "github.com/cockroachdb/crlfmt" + packages = ["."] + revision = "5895607e5ea790fcbb640260129a12d277b34e46" + +[[projects]] + branch = "master" + name = "github.com/cockroachdb/returncheck" + packages = ["."] + revision = "e91bb28baf9de4a530d3ae7f041953b23dcce9be" + +[[projects]] + branch = "master" + name = "github.com/cockroachdb/stress" + packages = ["."] + revision = "29b5d31b4c3a949cb3a726750bc34c4d58ec15e8" + +[[projects]] + branch = "master" + name = "github.com/cockroachdb/ttycolor" + packages = ["."] + revision = "5bed2b5a875c88a9d87d0f12b21ba156e2f995f7" + +[[projects]] + branch = "master" + name = "github.com/codahale/hdrhistogram" + packages = ["."] + revision = "3a0bb77429bd3a61596f5e8a3172445844342120" + +[[projects]] + branch = "master" + name = "github.com/coreos/etcd" + packages = [ + "raft", + "raft/raftpb", + ] + revision = "ce0ad377d21819592c3d8c29417d9d7a5ac53f2f" + +[[projects]] + name = "github.com/cpuguy83/go-md2man" + packages = ["md2man"] + revision = "1d903dcb749992f3741d744c0f8376b4bd7eb3e1" + version = "v1.0.7" + +[[projects]] + name = "github.com/davecgh/go-spew" + packages = ["spew"] + revision = "346938d642f2ec3594ed81d874461961cd0faa76" + version = "v1.1.0" + +[[projects]] + name = "github.com/dgrijalva/jwt-go" + packages = ["."] + revision = "dbeaa9332f19a944acb5736b4456cfcc02140e29" + version = "v3.1.0" + +[[projects]] + branch = "master" + name = "github.com/dgryski/go-metro" + packages = ["."] + revision = "280f6062b5bc97ee9b9afe7f2ccb361e59845baa" + +[[projects]] + branch = "master" + name = "github.com/docker/distribution" + packages = [ + "digestset", + "reference", + ] + revision = "3800056b8832cf6075e78b282ac010131d8687bc" + +[[projects]] + branch = "master" + name = "github.com/docker/docker" + packages = [ + "api", + "api/types", + "api/types/blkiodev", + "api/types/container", + "api/types/events", + "api/types/filters", + "api/types/image", + "api/types/mount", + "api/types/network", + "api/types/registry", + "api/types/strslice", + "api/types/swarm", + "api/types/swarm/runtime", + "api/types/time", + "api/types/versions", + "api/types/volume", + "client", + "pkg/jsonmessage", + "pkg/stdcopy", + "pkg/term", + "pkg/term/windows", + ] + revision = "3ab20a87fa10360df891a67b8307399cdf1ab429" + +[[projects]] + name = "github.com/docker/go-connections" + packages = [ + "nat", + "sockets", + "tlsconfig", + ] + revision = "3ede32e2033de7505e6500d6c868c2b9ed9f169d" + version = "v0.3.0" + +[[projects]] + name = "github.com/docker/go-units" + packages = ["."] + revision = "0dadbb0345b35ec7ef35e228dabb8de89a65bf52" + version = "v0.3.2" + +[[projects]] + branch = "master" + name = "github.com/dustin/go-humanize" + packages = ["."] + revision = "77ed807830b4df581417e7f89eb81d4872832b72" + +[[projects]] + name = "github.com/eapache/go-resiliency" + packages = ["breaker"] + revision = "6800482f2c813e689c88b7ed3282262385011890" + version = "v1.0.0" + +[[projects]] + branch = "master" + name = "github.com/eapache/go-xerial-snappy" + packages = ["."] + revision = "bb955e01b9346ac19dc29eb16586c90ded99a98c" + +[[projects]] + name = "github.com/eapache/queue" + packages = ["."] + revision = "ded5959c0d4e360646dc9e9908cff48666781367" + version = "v1.0.2" + +[[projects]] + name = "github.com/elastic/gosigar" + packages = [ + ".", + "sys/windows", + ] + revision = "306d51981789ccc65e5f1431d5c0d78d8c368f1b" + version = "v0.5.0" + +[[projects]] + name = "github.com/elazarl/go-bindata-assetfs" + packages = ["."] + revision = "30f82fa23fd844bd5bb1e5f216db87fd77b5eb43" + version = "v1.0.0" + +[[projects]] + branch = "master" + name = "github.com/facebookgo/clock" + packages = ["."] + revision = "600d898af40aa09a7a93ecb9265d87b0504b6f03" + +[[projects]] + branch = "master" + name = "github.com/getsentry/raven-go" + packages = ["."] + revision = "221b2b44fb33f84ed3ea13f3aed62ff48c85636b" + source = "https://github.com/cockroachdb/raven-go" + +[[projects]] + branch = "master" + name = "github.com/ghemawat/stream" + packages = ["."] + revision = "78e682abcae4f96ac7ddbe39912967a5f7cbbaa6" + +[[projects]] + name = "github.com/go-ini/ini" + packages = ["."] + revision = "f280b3ba517bf5fc98922624f21fb0e7a92adaec" + version = "v1.30.3" + +[[projects]] + name = "github.com/go-logfmt/logfmt" + packages = ["."] + revision = "390ab7935ee28ec6b286364bba9b4dd6410cb3d5" + version = "v0.3.0" + +[[projects]] + name = "github.com/go-ole/go-ole" + packages = [ + ".", + "oleutil", + ] + revision = "a41e3c4b706f6ae8dfbff342b06e40fa4d2d0506" + version = "v1.2.1" + +[[projects]] + branch = "master" + name = "github.com/go-sql-driver/mysql" + packages = ["."] + revision = "fade21009797158e7b79e04c340118a9220c6f9e" + +[[projects]] + branch = "v2" + name = "github.com/go-yaml/yaml" + packages = ["."] + revision = "eb3733d160e74a9c7e442f435eb3bea458e1d19f" + +[[projects]] + name = "github.com/gogo/protobuf" + packages = [ + "gogoproto", + "jsonpb", + "plugin/compare", + "plugin/defaultcheck", + "plugin/description", + "plugin/embedcheck", + "plugin/enumstringer", + "plugin/equal", + "plugin/face", + "plugin/gostring", + "plugin/marshalto", + "plugin/oneofcheck", + "plugin/populate", + "plugin/size", + "plugin/stringer", + "plugin/testgen", + "plugin/union", + "plugin/unmarshal", + "proto", + "protoc-gen-gogo/descriptor", + "protoc-gen-gogo/generator", + "protoc-gen-gogo/grpc", + "protoc-gen-gogo/plugin", + "sortkeys", + "types", + "vanity", + "vanity/command", + ] + revision = "1adfc126b41513cc696b209667c8656ea7aac67c" + version = "v1.0.0" + +[[projects]] + branch = "master" + name = "github.com/golang-commonmark/markdown" + packages = [ + ".", + "byteutil", + "html", + "linkify", + ] + revision = "11a7a839e723aa293cccdc353b394dbfce7c131e" + +[[projects]] + name = "github.com/golang/dep" + packages = [ + ".", + "cmd/dep", + "gps", + "gps/internal/pb", + "gps/paths", + "gps/pkgtree", + "internal/feedback", + "internal/fs", + "internal/importers", + "internal/importers/base", + "internal/importers/glide", + "internal/importers/glock", + "internal/importers/godep", + "internal/importers/govend", + "internal/importers/govendor", + "internal/importers/gvt", + "internal/importers/vndr", + ] + revision = "37d9ea0ac16f0e0a05afc3b60e1ac8c364b6c329" + version = "v0.4.1" + +[[projects]] + branch = "master" + name = "github.com/golang/glog" + packages = ["."] + revision = "23def4e6c14b4da8ac2ed8007337bc5eb5007998" + +[[projects]] + branch = "master" + name = "github.com/golang/leveldb" + packages = [ + "crc", + "db", + "memfs", + "table", + ] + revision = "259d9253d71996b7778a3efb4144fe4892342b18" + +[[projects]] + branch = "master" + name = "github.com/golang/lint" + packages = [ + ".", + "golint", + ] + revision = "6aaf7c34af0f4c36a57e0c429bace4d706d8e931" + +[[projects]] + branch = "master" + name = "github.com/golang/protobuf" + packages = [ + "jsonpb", + "proto", + "protoc-gen-go/descriptor", + "protoc-gen-go/generator", + "protoc-gen-go/generator/internal/remap", + "protoc-gen-go/plugin", + "ptypes", + "ptypes/any", + "ptypes/duration", + "ptypes/struct", + "ptypes/timestamp", + ] + revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265" + +[[projects]] + branch = "master" + name = "github.com/golang/snappy" + packages = ["."] + revision = "553a641470496b2327abcac10b36396bd98e45c9" + +[[projects]] + branch = "master" + name = "github.com/google/btree" + packages = ["."] + revision = "e89373fe6b4a7413d7acd6da1725b83ef713e6e4" + +[[projects]] + name = "github.com/google/go-github" + packages = ["github"] + revision = "e48060a28fac52d0f1cb758bc8b87c07bac4a87d" + version = "v15.0.0" + +[[projects]] + branch = "master" + name = "github.com/google/go-querystring" + packages = ["query"] + revision = "53e6ce116135b80d037921a7fdd5138cf32d7a8a" + +[[projects]] + branch = "master" + name = "github.com/google/pprof" + packages = [ + ".", + "driver", + "internal/binutils", + "internal/driver", + "internal/elfexec", + "internal/graph", + "internal/measurement", + "internal/plugin", + "internal/report", + "internal/symbolizer", + "internal/symbolz", + "profile", + "third_party/d3", + "third_party/d3flamegraph", + "third_party/d3tip", + "third_party/svgpan", + ] + revision = "60840899638bfaf3fd6f2b17b9785f1dac67a4a4" + +[[projects]] + name = "github.com/googleapis/gax-go" + packages = ["."] + revision = "317e0006254c44a0ac427cc52a0e083ff0b9622f" + version = "v2.0.0" + +[[projects]] + name = "github.com/gorilla/websocket" + packages = ["."] + revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b" + version = "v1.2.0" + +[[projects]] + name = "github.com/grpc-ecosystem/grpc-gateway" + packages = [ + "protoc-gen-grpc-gateway", + "protoc-gen-grpc-gateway/descriptor", + "protoc-gen-grpc-gateway/generator", + "protoc-gen-grpc-gateway/gengateway", + "protoc-gen-grpc-gateway/httprule", + "runtime", + "runtime/internal", + "utilities", + ] + revision = "07f5e79768022f9a3265235f0db4ac8c3f675fec" + version = "v1.3.1" + +[[projects]] + branch = "master" + name = "github.com/grpc-ecosystem/grpc-opentracing" + packages = ["go/otgrpc"] + revision = "a570af39704b9f3d4bb530d83192a99ea6337d5a" + +[[projects]] + branch = "master" + name = "github.com/hashicorp/go-version" + packages = ["."] + revision = "fc61389e27c71d120f87031ca8c88a3428f372dd" + +[[projects]] + branch = "master" + name = "github.com/ianlancetaylor/cgosymbolizer" + packages = ["."] + revision = "f5072df9c550dc687157e5d7efb50825cdf8f0eb" + +[[projects]] + branch = "master" + name = "github.com/ianlancetaylor/demangle" + packages = ["."] + revision = "4883227f66371e02c4948937d3e2be1664d9be38" + +[[projects]] + name = "github.com/inconshreveable/mousetrap" + packages = ["."] + revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75" + version = "v1.0" + +[[projects]] + name = "github.com/jackc/pgx" + packages = [ + ".", + "chunkreader", + "internal/sanitize", + "pgio", + "pgproto3", + "pgtype", + ] + revision = "da3231b0b66e2e74cdb779f1d46c5e958ba8be27" + version = "v3.1.0" + +[[projects]] + name = "github.com/jmank88/nuts" + packages = ["."] + revision = "8b28145dffc87104e66d074f62ea8080edfad7c8" + version = "v0.3.0" + +[[projects]] + name = "github.com/jmespath/go-jmespath" + packages = ["."] + revision = "0b12d6b5" + +[[projects]] + branch = "master" + name = "github.com/jteeuwen/go-bindata" + packages = [ + ".", + "go-bindata", + ] + revision = "6b667f85196e80d8420d634ca3fecca389a53207" + source = "https://github.com/dim13/go-bindata" + +[[projects]] + branch = "master" + name = "github.com/kisielk/errcheck" + packages = [ + ".", + "internal/errcheck", + ] + revision = "b1445a9dd8285a50c6d1661d16f0a9ceb08125f7" + +[[projects]] + branch = "master" + name = "github.com/kisielk/gotool" + packages = [ + ".", + "internal/load", + ] + revision = "d6ce6262d87e3a4e153e86023ff56ae771554a41" + +[[projects]] + name = "github.com/knz/go-libedit" + packages = [ + ".", + "common", + "other", + "unix", + "unix/sigtramp", + ] + revision = "69b759d6ff84df6bbd39846dae3670625cc0361b" + version = "v1.7" + +[[projects]] + branch = "master" + name = "github.com/knz/strtime" + packages = ["."] + revision = "813725a7c183ac304d1ed8fb7e37a567733e9c05" + +[[projects]] + branch = "master" + name = "github.com/kr/logfmt" + packages = ["."] + revision = "b84e30acd515aadc4b783ad4ff83aff3299bdfe0" + +[[projects]] + branch = "master" + name = "github.com/kr/pretty" + packages = ["."] + revision = "cfb55aafdaf3ec08f0db22699ab822c50091b1c4" + +[[projects]] + branch = "master" + name = "github.com/kr/text" + packages = ["."] + revision = "7cafcd837844e784b526369c9bce262804aebc60" + +[[projects]] + branch = "master" + name = "github.com/lib/pq" + packages = [ + ".", + "oid", + ] + revision = "27ea5d92de30060e7121ddd543fe14e9a327e0cc" + +[[projects]] + name = "github.com/lightstep/lightstep-tracer-go" + packages = [ + ".", + "collectorpb", + "lightstep_thrift", + "lightsteppb", + "thrift_0_9_2/lib/go/thrift", + ] + revision = "ba38bae1f0ec1ece9092d35bbecbb497ee344cbc" + version = "v0.15.0" + +[[projects]] + name = "github.com/marstr/guid" + packages = ["."] + revision = "8bd9a64bf37eb297b492a4101fb28e80ac0b290f" + version = "v1.1.0" + +[[projects]] + name = "github.com/marusama/semaphore" + packages = ["."] + revision = "567f17206eaa3f28bd73e209620e2abaa65d7405" + version = "2.1.1" + +[[projects]] + name = "github.com/mattn/go-isatty" + packages = ["."] + revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39" + version = "v0.0.3" + +[[projects]] + name = "github.com/mattn/go-runewidth" + packages = ["."] + revision = "9e777a8366cce605130a531d2cd6363d07ad7317" + version = "v0.0.2" + +[[projects]] + name = "github.com/mattn/goveralls" + packages = ["."] + revision = "b71a1e4855f87991aff01c2c833a75a07059c61c" + version = "v0.0.2" + +[[projects]] + name = "github.com/matttproud/golang_protobuf_extensions" + packages = ["pbutil"] + revision = "3247c84500bff8d9fb6d579d800f20b3e091582c" + version = "v1.0.0" + +[[projects]] + name = "github.com/mibk/dupl" + packages = [ + ".", + "job", + "output", + "suffixtree", + "syntax", + "syntax/golang", + ] + revision = "f008fcf5e62793d38bda510ee37aab8b0c68e76c" + version = "v1.0.0" + +[[projects]] + branch = "master" + name = "github.com/mitchellh/reflectwalk" + packages = ["."] + revision = "63d60e9d0dbc60cf9164e6510889b0db6683d98c" + +[[projects]] + branch = "master" + name = "github.com/montanaflynn/stats" + packages = ["."] + revision = "72625ec1691e40181ac5282a7f2ca4745952a7a7" + +[[projects]] + branch = "master" + name = "github.com/nightlyone/lockfile" + packages = ["."] + revision = "6a197d5ea61168f2ac821de2b7f011b250904900" + +[[projects]] + name = "github.com/nlopes/slack" + packages = ["."] + revision = "8ab4d0b364ef1e9af5d102531da20d5ec902b6c4" + version = "v0.2.0" + +[[projects]] + branch = "master" + name = "github.com/olekukonko/tablewriter" + packages = ["."] + revision = "b8a9be070da40449e501c3c4730a889e42d87a9e" + +[[projects]] + name = "github.com/opencontainers/go-digest" + packages = ["."] + revision = "279bed98673dd5bef374d3b6e4b09e2af76183bf" + version = "v1.0.0-rc1" + +[[projects]] + name = "github.com/opencontainers/image-spec" + packages = [ + "specs-go", + "specs-go/v1", + ] + revision = "ab7389ef9f50030c9b245bc16b981c7ddf192882" + version = "v1.0.0" + +[[projects]] + branch = "master" + name = "github.com/opennota/urlesc" + packages = ["."] + revision = "de5bf2ad457846296e2031421a34e2568e304e35" + +[[projects]] + branch = "master" + name = "github.com/opentracing-contrib/go-observer" + packages = ["."] + revision = "a52f2342449246d5bcc273e65cbdcfa5f7d6c63c" + +[[projects]] + name = "github.com/opentracing/opentracing-go" + packages = [ + ".", + "ext", + "log", + ] + revision = "1949ddbfd147afd4d964a9f00b24eb291e0e7c38" + version = "v1.0.2" + +[[projects]] + name = "github.com/openzipkin/zipkin-go-opentracing" + packages = [ + ".", + "flag", + "thrift/gen-go/scribe", + "thrift/gen-go/zipkincore", + "types", + "wire", + ] + revision = "45e90b00710a4c34a1a7d8a78d90f9b010b0bd4d" + version = "v0.3.2" + +[[projects]] + branch = "master" + name = "github.com/pelletier/go-toml" + packages = ["."] + revision = "05bcc0fb0d3e60da4b8dd5bd7e0ea563eb4ca943" + +[[projects]] + branch = "master" + name = "github.com/petar/GoLLRB" + packages = ["llrb"] + revision = "53be0d36a84c2a886ca057d34b6aa4468df9ccb4" + +[[projects]] + branch = "master" + name = "github.com/petermattis/goid" + packages = ["."] + revision = "b0b1615b78e5ee59739545bb38426383b2cda4c9" + +[[projects]] + name = "github.com/pierrec/lz4" + packages = ["."] + revision = "08c27939df1bd95e881e2c2367a749964ad1fceb" + version = "v1.0.1" + +[[projects]] + name = "github.com/pierrec/xxHash" + packages = ["xxHash32"] + revision = "f051bb7f1d1aaf1b5a665d74fb6b0217712c69f7" + version = "v0.1.1" + +[[projects]] + name = "github.com/pkg/errors" + packages = ["."] + revision = "645ef00459ed84a119197bfb8d8205042c6df63d" + version = "v0.8.0" + +[[projects]] + name = "github.com/pmezard/go-difflib" + packages = ["difflib"] + revision = "792786c7400a136282c1664665ae0a8db921c6c2" + version = "v1.0.0" + +[[projects]] + name = "github.com/prometheus/client_golang" + packages = [ + "prometheus", + "prometheus/graphite", + ] + revision = "967789050ba94deca04a5e84cce8ad472ce313c1" + version = "v0.9.0-pre1" + +[[projects]] + branch = "master" + name = "github.com/prometheus/client_model" + packages = ["go"] + revision = "6f3806018612930941127f2a7c6c453ba2c527d2" + +[[projects]] + branch = "master" + name = "github.com/prometheus/common" + packages = [ + "expfmt", + "internal/bitbucket.org/ww/goautoneg", + "model", + ] + revision = "1bab55dd05dbff384524a6a1c99006d9eb5f139b" + +[[projects]] + branch = "master" + name = "github.com/prometheus/procfs" + packages = [ + ".", + "internal/util", + "nfs", + "xfs", + ] + revision = "8b1c2da0d56deffdbb9e48d4414b4e674bd8083e" + +[[projects]] + branch = "master" + name = "github.com/rcrowley/go-metrics" + packages = [ + ".", + "exp", + ] + revision = "1f30fe9094a513ce4c700b9a54458bbb0c96996c" + +[[projects]] + branch = "master" + name = "github.com/rubyist/circuitbreaker" + packages = ["."] + revision = "2074adba5ddc7d5f7559448a9c3066573521c5bf" + +[[projects]] + name = "github.com/russross/blackfriday" + packages = ["."] + revision = "4048872b16cc0fc2c5fd9eacf0ed2c2fedaa0c8c" + version = "v1.5" + +[[projects]] + name = "github.com/sasha-s/go-deadlock" + packages = ["."] + revision = "03d40e5dbd5488667a13b3c2600b2f7c2886f02f" + version = "v0.2.0" + +[[projects]] + name = "github.com/satori/go.uuid" + packages = ["."] + revision = "f58768cc1a7a7e77a3bd49e98cdd21419399b6a3" + version = "v1.2.0" + +[[projects]] + branch = "master" + name = "github.com/sdboyer/constext" + packages = ["."] + revision = "836a144573533ea4da4e6929c235fd348aed1c80" + +[[projects]] + name = "github.com/sirupsen/logrus" + packages = ["."] + revision = "f006c2ac4710855cf0f916dd6b77acf6b048dc6e" + version = "v1.0.3" + +[[projects]] + name = "github.com/spf13/cobra" + packages = [ + ".", + "doc", + ] + revision = "7b2c5ac9fc04fc5efafb60700713d4fa609b777b" + version = "v0.0.1" + +[[projects]] + name = "github.com/spf13/pflag" + packages = ["."] + revision = "e57e3eeb33f795204c1ca35f56c44f83227c6e66" + version = "v1.0.0" + +[[projects]] + name = "github.com/stretchr/testify" + packages = [ + "assert", + "require", + ] + revision = "69483b4bd14f5845b5a1e55bca19e954e827f1d0" + version = "v1.1.4" + +[[projects]] + branch = "master" + name = "github.com/wadey/gocovmerge" + packages = ["."] + revision = "b5bfa59ec0adc420475f97f89b58045c721d761c" + +[[projects]] + branch = "master" + name = "github.com/xwb1989/sqlparser" + packages = [ + ".", + "dependency/bytes2", + "dependency/hack", + "dependency/querypb", + "dependency/sqltypes", + ] + revision = "6aff8615a33faf1f260a3a8f800456511505d5fd" + source = "https://github.com/dt/sqlparser" + +[[projects]] + name = "go.opencensus.io" + packages = [ + "exporter/stackdriver/propagation", + "internal", + "internal/tagencoding", + "plugin/ochttp", + "plugin/ochttp/propagation/b3", + "stats", + "stats/internal", + "stats/view", + "tag", + "trace", + "trace/internal", + "trace/propagation", + ] + revision = "0095aec66ae14801c6711210f6f0716411cefdd3" + version = "v0.8.0" + +[[projects]] + branch = "master" + name = "golang.org/x/crypto" + packages = [ + "bcrypt", + "blowfish", + "ssh/terminal", + ] + revision = "bd6f299fb381e4c3393d1c4b1f0b94f5e77650c8" + +[[projects]] + branch = "master" + name = "golang.org/x/net" + packages = [ + "context", + "context/ctxhttp", + "html", + "html/atom", + "http2", + "http2/hpack", + "idna", + "internal/timeseries", + "lex/httplex", + "proxy", + "trace", + ] + revision = "c73622c77280266305273cb545f54516ced95b93" + +[[projects]] + branch = "master" + name = "golang.org/x/oauth2" + packages = [ + ".", + "google", + "internal", + "jws", + "jwt", + ] + revision = "6881fee410a5daf86371371f9ad451b95e168b71" + +[[projects]] + branch = "master" + name = "golang.org/x/perf" + packages = [ + "benchstat", + "cmd/benchstat", + "internal/stats", + "storage", + "storage/benchfmt", + ] + revision = "4469e6ce8cc3920f1b42128b9d557bea2e08621a" + +[[projects]] + branch = "master" + name = "golang.org/x/sync" + packages = [ + "errgroup", + "syncmap", + ] + revision = "8e0aa688b654ef28caa72506fa5ec8dba9fc7690" + +[[projects]] + branch = "master" + name = "golang.org/x/sys" + packages = [ + "unix", + "windows", + ] + revision = "95c6576299259db960f6c5b9b69ea52422860fce" + +[[projects]] + name = "golang.org/x/text" + packages = [ + "collate", + "collate/build", + "internal/colltab", + "internal/gen", + "internal/tag", + "internal/triegen", + "internal/ucd", + "language", + "secure/bidirule", + "transform", + "unicode/bidi", + "unicode/cldr", + "unicode/norm", + "unicode/rangetable", + ] + revision = "470f45bf29f4147d6fbd7dfd0a02a848e49f5bf4" + +[[projects]] + branch = "master" + name = "golang.org/x/time" + packages = ["rate"] + revision = "6dc17368e09b0e8634d71cac8168d853e869a0c7" + +[[projects]] + branch = "master" + name = "golang.org/x/tools" + packages = [ + "cmd/goimports", + "cmd/goyacc", + "cmd/stringer", + "container/intsets", + "cover", + "go/ast/astutil", + "go/buildutil", + "go/gcexportdata", + "go/gcimporter15", + "go/loader", + "go/types/typeutil", + "imports", + ] + revision = "90b807ada4cc7ab5d1409d637b1f3beb5a776be7" + +[[projects]] + branch = "master" + name = "google.golang.org/api" + packages = [ + "gensupport", + "googleapi", + "googleapi/internal/uritemplates", + "googleapi/transport", + "internal", + "iterator", + "option", + "storage/v1", + "transport/http", + ] + revision = "9c79deebf7496e355d7e95d82d4af1fe4e769b2f" + +[[projects]] + name = "google.golang.org/appengine" + packages = [ + ".", + "internal", + "internal/app_identity", + "internal/base", + "internal/datastore", + "internal/log", + "internal/modules", + "internal/remote_api", + "internal/urlfetch", + "urlfetch", + ] + revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a" + version = "v1.0.0" + +[[projects]] + branch = "master" + name = "google.golang.org/genproto" + packages = [ + "googleapis/api/annotations", + "googleapis/iam/v1", + "googleapis/rpc/code", + "googleapis/rpc/status", + ] + revision = "f676e0f3ac6395ff1a529ae59a6670878a8371a6" + +[[projects]] + branch = "master" + name = "google.golang.org/grpc" + packages = [ + ".", + "balancer", + "balancer/base", + "balancer/roundrobin", + "channelz", + "codes", + "connectivity", + "credentials", + "encoding", + "encoding/proto", + "grpclb/grpc_lb_v1/messages", + "grpclog", + "health/grpc_health_v1", + "internal", + "keepalive", + "metadata", + "naming", + "peer", + "resolver", + "resolver/dns", + "resolver/passthrough", + "stats", + "status", + "tap", + "transport", + ] + revision = "8f06f82ca394b1ac837d4b0c0cfa07188b0e9dee" + +[[projects]] + branch = "v2" + name = "gopkg.in/yaml.v2" + packages = ["."] + revision = "287cf08546ab5e7e37d55a84f7ed3fd1db036de5" + +[[projects]] + name = "honnef.co/go/tools" + packages = [ + "callgraph", + "callgraph/static", + "deprecated", + "functions", + "internal/sharedcheck", + "lint", + "simple", + "ssa", + "ssa/ssautil", + "staticcheck", + "staticcheck/vrp", + "unused", + ] + revision = "d73ab98e7c39fdcf9ba65062e43d34310f198353" + version = "2017.2.2" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "8022836223059aead87f6d7d6b9451a5d7ad8a20f4358cb709ccf2859f9c74ce" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/spec/fixtures/go/gopkg_locks/fider.lock b/spec/fixtures/go/gopkg_locks/fider.lock new file mode 100644 index 00000000000..3e1ec9742b6 --- /dev/null +++ b/spec/fixtures/go/gopkg_locks/fider.lock @@ -0,0 +1,136 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + name = "cloud.google.com/go" + packages = ["compute/metadata"] + revision = "5a9e19d4e1e41a734154e44a2132b358afb49a03" + version = "v0.13.0" + +[[projects]] + name = "github.com/dgrijalva/jwt-go" + packages = ["."] + revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e" + version = "v3.2.0" + +[[projects]] + branch = "master" + name = "github.com/goenning/letteravatar" + packages = ["."] + revision = "553181ed4055278e356ce21812adb34ae487f88e" + +[[projects]] + branch = "master" + name = "github.com/golang/freetype" + packages = [ + ".", + "raster", + "truetype" + ] + revision = "e2365dfdc4a05e4b8299a783240d4a7d5a65d4e4" + +[[projects]] + branch = "master" + name = "github.com/golang/protobuf" + packages = ["proto"] + revision = "ae59567b9aab61b50b2590679a62c3c044030b61" + +[[projects]] + name = "github.com/gosimple/slug" + packages = ["."] + revision = "e9f42fa127660e552d0ad2b589868d403a9be7c6" + version = "v1.1.1" + +[[projects]] + branch = "master" + name = "github.com/julienschmidt/httprouter" + packages = ["."] + revision = "adbc77eec0d91467376ca515bc3a14b8434d0f18" + +[[projects]] + branch = "master" + name = "github.com/lib/pq" + packages = [ + ".", + "oid" + ] + revision = "90697d60dd844d5ef6ff15135d0203f65d2f53b8" + +[[projects]] + branch = "master" + name = "github.com/rainycape/unidecode" + packages = ["."] + revision = "cb7f23ec59bec0d61b19c56cd88cee3d0cc1870c" + +[[projects]] + name = "github.com/russross/blackfriday" + packages = ["."] + revision = "55d61fa8aa702f59229e6cff85793c22e580eaf5" + version = "v1.5.1" + +[[projects]] + branch = "master" + name = "golang.org/x/crypto" + packages = [ + "acme", + "acme/autocert" + ] + revision = "2c241ca3045ddc354463c376a9515d9f1f1390a4" + +[[projects]] + branch = "master" + name = "golang.org/x/image" + packages = [ + "draw", + "font", + "math/f64", + "math/fixed" + ] + revision = "e20db36d77bd0cb36cea8fe49d5c37d82d21591f" + +[[projects]] + branch = "master" + name = "golang.org/x/net" + packages = [ + "context", + "context/ctxhttp" + ] + revision = "8351a756f30f1297fe94bbf4b767ec589c6ea6d0" + +[[projects]] + branch = "master" + name = "golang.org/x/oauth2" + packages = [ + ".", + "facebook", + "github", + "google", + "internal", + "jws", + "jwt" + ] + revision = "ef147856a6ddbb60760db74283d2424e98c87bff" + +[[projects]] + name = "google.golang.org/appengine" + packages = [ + ".", + "internal", + "internal/app_identity", + "internal/base", + "internal/datastore", + "internal/log", + "internal/modules", + "internal/remote_api", + "internal/urlfetch", + "urlfetch" + ] + revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a" + version = "v1.0.0" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "01ff2ef749623d2bc5d0d46238992ffb3385f10d53ff2345ef6898b29fbb4bfd" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/spec/fixtures/go/gopkg_locks/flagr.lock b/spec/fixtures/go/gopkg_locks/flagr.lock new file mode 100644 index 00000000000..a938ccf8e14 --- /dev/null +++ b/spec/fixtures/go/gopkg_locks/flagr.lock @@ -0,0 +1,434 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + name = "github.com/DataDog/datadog-go" + packages = ["statsd"] + revision = "e67964b4021ad3a334e748e8811eb3cd6becbc6e" + version = "2.1.0" + +[[projects]] + name = "github.com/PuerkitoBio/purell" + packages = ["."] + revision = "0bcb03f4b4d0a9428594752bd2a3b9aa0a9d4bd4" + version = "v1.1.0" + +[[projects]] + branch = "master" + name = "github.com/PuerkitoBio/urlesc" + packages = ["."] + revision = "de5bf2ad457846296e2031421a34e2568e304e35" + +[[projects]] + name = "github.com/Shopify/sarama" + packages = ["."] + revision = "35324cf48e33d8260e1c7c18854465a904ade249" + version = "v1.17.0" + +[[projects]] + name = "github.com/asaskevich/govalidator" + packages = ["."] + revision = "ccb8e960c48f04d6935e72476ae4a51028f9e22f" + version = "v9" + +[[projects]] + branch = "master" + name = "github.com/auth0/go-jwt-middleware" + packages = ["."] + revision = "5493cabe49f7bfa6e2ec444a09d334d90cd4e2bd" + +[[projects]] + branch = "master" + name = "github.com/bouk/monkey" + packages = ["."] + revision = "5df1f207ff77e025801505ae4d903133a0b4353f" + +[[projects]] + branch = "master" + name = "github.com/brandur/simplebox" + packages = ["."] + revision = "84e9865bb03ad38c464043bf5382ce8c68ca5f0c" + +[[projects]] + name = "github.com/bsm/ratelimit" + packages = ["."] + revision = "4a38dca83ebadb017e83301642fab61b1c17edb7" + version = "v2.0.0" + +[[projects]] + name = "github.com/caarlos0/env" + packages = ["."] + revision = "1cddc31c48c56ecd700d873edb9fd5b6f5df922a" + version = "v3.3.0" + +[[projects]] + name = "github.com/certifi/gocertifi" + packages = ["."] + revision = "deb3ae2ef2610fde3330947281941c562861188b" + version = "2018.01.18" + +[[projects]] + name = "github.com/davecgh/go-spew" + packages = ["spew"] + revision = "346938d642f2ec3594ed81d874461961cd0faa76" + version = "v1.1.0" + +[[projects]] + name = "github.com/dgrijalva/jwt-go" + packages = ["."] + revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e" + version = "v3.2.0" + +[[projects]] + name = "github.com/docker/go-units" + packages = ["."] + revision = "47565b4f722fb6ceae66b95f853feed578a4a51c" + version = "v0.3.3" + +[[projects]] + name = "github.com/eapache/go-resiliency" + packages = ["breaker"] + revision = "ea41b0fad31007accc7f806884dcdf3da98b79ce" + version = "v1.1.0" + +[[projects]] + branch = "master" + name = "github.com/eapache/go-xerial-snappy" + packages = ["."] + revision = "bb955e01b9346ac19dc29eb16586c90ded99a98c" + +[[projects]] + name = "github.com/eapache/queue" + packages = ["."] + revision = "44cc805cf13205b55f69e14bcb69867d1ae92f98" + version = "v1.1.0" + +[[projects]] + name = "github.com/evalphobia/logrus_sentry" + packages = ["."] + revision = "b78b27461c8163c45abf4ab3a8330d2b1ee9456a" + version = "v0.4.6" + +[[projects]] + branch = "master" + name = "github.com/getsentry/raven-go" + packages = ["."] + revision = "ed7bcb39ff10f39ab08e317ce16df282845852fa" + +[[projects]] + branch = "master" + name = "github.com/go-openapi/analysis" + packages = ["."] + revision = "5957818e100395077187fb7ef3b8a28227af06c6" + +[[projects]] + branch = "master" + name = "github.com/go-openapi/errors" + packages = ["."] + revision = "b2b2befaf267d082d779bcef52d682a47c779517" + +[[projects]] + branch = "master" + name = "github.com/go-openapi/jsonpointer" + packages = ["."] + revision = "3a0015ad55fa9873f41605d3e8f28cd279c32ab2" + +[[projects]] + branch = "master" + name = "github.com/go-openapi/jsonreference" + packages = ["."] + revision = "3fb327e6747da3043567ee86abd02bb6376b6be2" + +[[projects]] + branch = "master" + name = "github.com/go-openapi/loads" + packages = ["."] + revision = "2a2b323bab96e6b1fdee110e57d959322446e9c9" + +[[projects]] + branch = "master" + name = "github.com/go-openapi/runtime" + packages = [ + ".", + "flagext", + "logger", + "middleware", + "middleware/denco", + "middleware/header", + "middleware/untyped", + "security" + ] + revision = "90c7afbb4bd8b22431e1c32d65caf405abf170c3" + +[[projects]] + branch = "master" + name = "github.com/go-openapi/spec" + packages = ["."] + revision = "bcff419492eeeb01f76e77d2ebc714dc97b607f5" + +[[projects]] + branch = "master" + name = "github.com/go-openapi/strfmt" + packages = ["."] + revision = "481808443b00a14745fada967cb5eeff0f9b1df2" + +[[projects]] + branch = "master" + name = "github.com/go-openapi/swag" + packages = ["."] + revision = "811b1089cde9dad18d4d0c2d09fbdbf28dbd27a5" + +[[projects]] + branch = "master" + name = "github.com/go-openapi/validate" + packages = ["."] + revision = "b0a3ed684d0fdd3e1eda00433382188ce8aa7169" + +[[projects]] + name = "github.com/go-sql-driver/mysql" + packages = ["."] + revision = "d523deb1b23d913de5bdada721a6071e71283618" + version = "v1.4.0" + +[[projects]] + branch = "master" + name = "github.com/gohttp/pprof" + packages = ["."] + revision = "c9d246cbb3ba6407f8b1decdfff558f0fe8f07e8" + +[[projects]] + branch = "master" + name = "github.com/golang/snappy" + packages = ["."] + revision = "2e65f85255dbc3072edf28d6b5b8efc472979f5a" + +[[projects]] + name = "github.com/jessevdk/go-flags" + packages = ["."] + revision = "c6ca198ec95c841fdb89fc0de7496fed11ab854e" + version = "v1.4.0" + +[[projects]] + name = "github.com/jinzhu/gorm" + packages = [ + ".", + "dialects/mysql", + "dialects/postgres", + "dialects/sqlite" + ] + revision = "6ed508ec6a4ecb3531899a69cbc746ccf65a4166" + version = "v1.9.1" + +[[projects]] + branch = "master" + name = "github.com/jinzhu/inflection" + packages = ["."] + revision = "04140366298a54a039076d798123ffa108fff46c" + +[[projects]] + branch = "master" + name = "github.com/lib/pq" + packages = [ + ".", + "hstore", + "oid" + ] + revision = "90697d60dd844d5ef6ff15135d0203f65d2f53b8" + +[[projects]] + branch = "master" + name = "github.com/mailru/easyjson" + packages = [ + "buffer", + "jlexer", + "jwriter" + ] + revision = "3fdea8d05856a0c8df22ed4bc71b3219245e4485" + +[[projects]] + name = "github.com/mattn/go-sqlite3" + packages = ["."] + revision = "25ecb14adfc7543176f7d85291ec7dba82c6f7e4" + version = "v1.9.0" + +[[projects]] + branch = "master" + name = "github.com/meatballhat/negroni-logrus" + packages = ["."] + revision = "31067281800f66f57548a7a32d9c6c5f963fef83" + +[[projects]] + branch = "master" + name = "github.com/mitchellh/mapstructure" + packages = ["."] + revision = "bb74f1db0675b241733089d5a1faa5dd8b0ef57b" + +[[projects]] + name = "github.com/newrelic/go-agent" + packages = [ + ".", + "internal", + "internal/cat", + "internal/jsonx", + "internal/logger", + "internal/sysinfo", + "internal/utilization" + ] + revision = "a5d75f2b4787aa1bbc76688187b2353f8ff581a6" + version = "v2.0.0" + +[[projects]] + name = "github.com/pierrec/lz4" + packages = [ + ".", + "internal/xxh32" + ] + revision = "6b9367c9ff401dbc54fabce3fb8d972e799b702d" + version = "v2.0.2" + +[[projects]] + name = "github.com/pkg/errors" + packages = ["."] + revision = "645ef00459ed84a119197bfb8d8205042c6df63d" + version = "v0.8.0" + +[[projects]] + name = "github.com/pmezard/go-difflib" + packages = ["difflib"] + revision = "792786c7400a136282c1664665ae0a8db921c6c2" + version = "v1.0.0" + +[[projects]] + branch = "master" + name = "github.com/prashantv/gostub" + packages = ["."] + revision = "5c68b99bb08825598e70739c40603c901ee58dba" + +[[projects]] + branch = "master" + name = "github.com/rcrowley/go-metrics" + packages = ["."] + revision = "e2704e165165ec55d062f5919b4b29494e9fa790" + +[[projects]] + name = "github.com/rs/cors" + packages = ["."] + revision = "ca016a06a5753f8ba03029c0aa5e54afb1bf713f" + version = "v1.4.0" + +[[projects]] + name = "github.com/sirupsen/logrus" + packages = ["."] + revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc" + version = "v1.0.5" + +[[projects]] + name = "github.com/spf13/cast" + packages = ["."] + revision = "8965335b8c7107321228e3e3702cab9832751bac" + version = "v1.2.0" + +[[projects]] + name = "github.com/stretchr/testify" + packages = ["assert"] + revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686" + version = "v1.2.2" + +[[projects]] + name = "github.com/urfave/negroni" + packages = ["."] + revision = "5dbbc83f748fc3ad38585842b0aedab546d0ea1e" + version = "v0.3.0" + +[[projects]] + branch = "master" + name = "github.com/yadvendar/negroni-newrelic-go-agent" + packages = ["."] + revision = "3dc58758cb67abc45ae91e8e7bb3d90bdc254dfb" + +[[projects]] + branch = "master" + name = "github.com/zhouzhuojie/conditions" + packages = ["."] + revision = "34882afc2ef77651b9e10ec8148ac7f95887f822" + +[[projects]] + branch = "master" + name = "golang.org/x/crypto" + packages = [ + "internal/subtle", + "nacl/secretbox", + "poly1305", + "salsa20/salsa", + "ssh/terminal" + ] + revision = "027cca12c2d63e3d62b670d901e8a2c95854feec" + +[[projects]] + branch = "master" + name = "golang.org/x/net" + packages = [ + "context", + "idna", + "netutil" + ] + revision = "db08ff08e8622530d9ed3a0e8ac279f6d4c02196" + +[[projects]] + branch = "master" + name = "golang.org/x/sys" + packages = [ + "unix", + "windows" + ] + revision = "6c888cc515d3ed83fc103cf1d84468aad274b0a7" + +[[projects]] + name = "golang.org/x/text" + packages = [ + "collate", + "collate/build", + "internal/colltab", + "internal/gen", + "internal/tag", + "internal/triegen", + "internal/ucd", + "language", + "secure/bidirule", + "transform", + "unicode/bidi", + "unicode/cldr", + "unicode/norm", + "unicode/rangetable", + "width" + ] + revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" + version = "v0.3.0" + +[[projects]] + name = "google.golang.org/appengine" + packages = ["cloudsql"] + revision = "b1f26356af11148e710935ed1ac8a7f5702c7612" + version = "v1.1.0" + +[[projects]] + branch = "v2" + name = "gopkg.in/mgo.v2" + packages = [ + "bson", + "internal/json" + ] + revision = "3f83fa5005286a7fe593b055f0d7771a7dce4655" + +[[projects]] + name = "gopkg.in/yaml.v2" + packages = ["."] + revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183" + version = "v2.2.1" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "a462c9ef93c43ec0d3e02c3a988cef9a4b411d5db504c79ed6372a23724c2124" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/spec/fixtures/go/gopkg_tomls/cockroach.toml b/spec/fixtures/go/gopkg_tomls/cockroach.toml new file mode 100644 index 00000000000..ad9e9230369 --- /dev/null +++ b/spec/fixtures/go/gopkg_tomls/cockroach.toml @@ -0,0 +1,112 @@ +required = [ + "github.com/client9/misspell/cmd/misspell", + "github.com/cockroachdb/crlfmt", + "github.com/cockroachdb/stress", + "github.com/golang/dep/cmd/dep", + "github.com/golang/lint/golint", + "github.com/google/pprof", + "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway", + "github.com/jteeuwen/go-bindata/go-bindata", + "github.com/kisielk/errcheck", + "github.com/mattn/goveralls", + "github.com/mibk/dupl", + "github.com/wadey/gocovmerge", + "golang.org/x/perf/cmd/benchstat", + "golang.org/x/tools/cmd/goimports", + "golang.org/x/tools/cmd/goyacc", + "golang.org/x/tools/cmd/stringer", +] + +ignored = [ + # Non-existent package used by a toy program in c-deps/protobuf. + "github.com/google/protobuf/examples/tutorial", +] + +# The collation tables must never change. +[[constraint]] + name = "golang.org/x/text" + revision = "470f45bf29f4147d6fbd7dfd0a02a848e49f5bf4" + +# https://github.com/coreos/etcd/commit/f03ed33 +# +# https://github.com/coreos/etcd/commit/ce0ad377 +[[constraint]] + name = "github.com/coreos/etcd" + branch = "master" + +# Used for the API client; we want the latest. +[[constraint]] + name = "github.com/docker/docker" + branch = "master" + +# https://github.com/getsentry/raven-go/pull/139 +[[constraint]] + name = "github.com/getsentry/raven-go" + source = "https://github.com/cockroachdb/raven-go" + +# Used for benchmarks, should be recent. +[[constraint]] + name = "github.com/go-sql-driver/mysql" + branch = "master" + +# https://github.com/jteeuwen/go-bindata/pull/158 +[[constraint]] + name = "github.com/jteeuwen/go-bindata" + source = "https://github.com/dim13/go-bindata" + branch = "master" + +# https://github.com/montanaflynn/stats/releases (latest is 2015-10-14) +[[constraint]] + name = "github.com/montanaflynn/stats" + branch = "master" + +# https://github.com/rubyist/circuitbreaker/commit/af95830 +[[constraint]] + name = "github.com/rubyist/circuitbreaker" + branch = "master" + +# https://github.com/xwb1989/sqlparser/issues/31 +# https://github.com/xwb1989/sqlparser/issues/32 +[[constraint]] + name = "github.com/xwb1989/sqlparser" + source = "https://github.com/dt/sqlparser" + +# The master version of go.uuid has an incompatible interface and (as +# of 2018-06-06) a serious bug. Don't upgrade without making sure +# that bug is fixed. +# https://github.com/cockroachdb/cockroach/issues/26332 +[[constraint]] + name = "github.com/satori/go.uuid" + version = "v1.2.0" + +# github.com/docker/docker depends on a few functions not included in the +# latest release: reference.{FamiliarName,ParseNormalizedNamed,TagNameOnly}. +# +# https://github.com/docker/distribution/commit/429c75f +# https://github.com/docker/distribution/commit/2caeb61 +[[override]] + name = "github.com/docker/distribution" + branch = "master" + +[prune] + go-tests = true + unused-packages = true + + # Avoid pruning projects containing needed protos. + + [[prune.project]] + name = "github.com/gogo/protobuf" + unused-packages = false + + [[prune.project]] + name = "github.com/grpc-ecosystem/grpc-gateway" + unused-packages = false + + [[prune.project]] + name = "github.com/prometheus/client_model" + unused-packages = false + + # Contains packages with used C files. + [[prune.project]] + name = "github.com/knz/go-libedit" + unused-packages = false diff --git a/spec/fixtures/go/gopkg_tomls/fider.toml b/spec/fixtures/go/gopkg_tomls/fider.toml new file mode 100644 index 00000000000..c3f72148435 --- /dev/null +++ b/spec/fixtures/go/gopkg_tomls/fider.toml @@ -0,0 +1,32 @@ + +[[constraint]] + name = "github.com/dgrijalva/jwt-go" + version = "^3.2.0" + +[[constraint]] + name = "github.com/gosimple/slug" + version = "^1.1.1" + +[[constraint]] + branch = "master" + name = "github.com/lib/pq" + +[[constraint]] + name = "github.com/russross/blackfriday" + version = "^1.5.1" + +[[constraint]] + branch = "master" + name = "golang.org/x/oauth2" + +[[constraint]] + branch = "master" + name = "github.com/goenning/letteravatar" + +[[constraint]] + branch = "master" + name = "github.com/julienschmidt/httprouter" + +[[constraint]] + branch = "master" + name = "golang.org/x/crypto" diff --git a/spec/fixtures/go/gopkg_tomls/flagr.toml b/spec/fixtures/go/gopkg_tomls/flagr.toml new file mode 100644 index 00000000000..a0c0535f591 --- /dev/null +++ b/spec/fixtures/go/gopkg_tomls/flagr.toml @@ -0,0 +1,154 @@ +# Gopkg.toml example +# +# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" +# +# [prune] +# non-go = false +# go-tests = true +# unused-packages = true + + +[[constraint]] + name = "github.com/DataDog/datadog-go" + version = "2.1.0" + +[[constraint]] + name = "github.com/Shopify/sarama" + version = "1.17.0" + +[[constraint]] + branch = "master" + name = "github.com/auth0/go-jwt-middleware" + +[[constraint]] + branch = "master" + name = "github.com/brandur/simplebox" + +[[constraint]] + name = "github.com/bsm/ratelimit" + version = "2.0.0" + +[[constraint]] + name = "github.com/caarlos0/env" + version = "3.3.0" + +[[constraint]] + name = "github.com/davecgh/go-spew" + version = "1.1.0" + +[[constraint]] + name = "github.com/dgrijalva/jwt-go" + version = "3.2.0" + +[[constraint]] + name = "github.com/evalphobia/logrus_sentry" + version = "0.4.6" + +[[constraint]] + branch = "master" + name = "github.com/getsentry/raven-go" + +[[constraint]] + branch = "master" + name = "github.com/go-openapi/errors" + +[[constraint]] + branch = "master" + name = "github.com/go-openapi/loads" + +[[constraint]] + branch = "master" + name = "github.com/go-openapi/runtime" + +[[constraint]] + branch = "master" + name = "github.com/go-openapi/spec" + +[[constraint]] + branch = "master" + name = "github.com/go-openapi/strfmt" + +[[constraint]] + branch = "master" + name = "github.com/go-openapi/swag" + +[[constraint]] + branch = "master" + name = "github.com/go-openapi/validate" + +[[constraint]] + branch = "master" + name = "github.com/gohttp/pprof" + +[[constraint]] + name = "github.com/jessevdk/go-flags" + version = "1.4.0" + +[[constraint]] + name = "github.com/jinzhu/gorm" + version = "1.9.1" + +[[constraint]] + branch = "master" + name = "github.com/meatballhat/negroni-logrus" + +[[constraint]] + name = "github.com/newrelic/go-agent" + version = "2.0.0" + +[[constraint]] + branch = "master" + name = "github.com/prashantv/gostub" + +[[constraint]] + name = "github.com/rs/cors" + version = "1.4.0" + +[[constraint]] + name = "github.com/sirupsen/logrus" + version = "1.0.5" + +[[constraint]] + name = "github.com/spf13/cast" + version = "1.2.0" + +[[constraint]] + name = "github.com/stretchr/testify" + version = "1.2.2" + +[[constraint]] + name = "github.com/urfave/negroni" + version = "0.3.0" + +[[constraint]] + branch = "master" + name = "github.com/yadvendar/negroni-newrelic-go-agent" + +[[constraint]] + branch = "master" + name = "github.com/zhouzhuojie/conditions" + +[[constraint]] + branch = "master" + name = "golang.org/x/net" + +[prune] + go-tests = true + unused-packages = true From 1961d0d4c5913e4cd464a0b1faec972451aaefb4 Mon Sep 17 00:00:00 2001 From: Grey Baker Date: Sat, 21 Jul 2018 23:10:22 +0100 Subject: [PATCH 04/28] Dep: Add basic MetadataFinder --- lib/dependabot/metadata_finders/go/dep.rb | 19 +++++- .../metadata_finders/go/dep_spec.rb | 61 +++++++++++++++++++ 2 files changed, 77 insertions(+), 3 deletions(-) diff --git a/lib/dependabot/metadata_finders/go/dep.rb b/lib/dependabot/metadata_finders/go/dep.rb index b95709af550..5fd114d6e2a 100644 --- a/lib/dependabot/metadata_finders/go/dep.rb +++ b/lib/dependabot/metadata_finders/go/dep.rb @@ -9,9 +9,22 @@ class Dep < Dependabot::MetadataFinders::Base private def look_up_source - # Hit the registry (or some other source) and get details of the - # location of the source code for the given dependency - Source.new(host: "github.com", repo: "my-org/my-dependency") + # TODO: A more general way to do this? + source_string = specified_source_string. + gsub(%r{^golang\.org/x}, "github.com/golang") + + Source.from_url(source_string) + end + + def specified_source_string + sources = dependency.requirements. + map { |r| r.fetch(:source) }.uniq.compact + + raise "Multiple sources! #{sources.join(', ')}" if sources.count > 1 + + sources.first&.fetch(:source, nil) || + sources.first&.fetch("source") || + dependency.name end end end diff --git a/spec/dependabot/metadata_finders/go/dep_spec.rb b/spec/dependabot/metadata_finders/go/dep_spec.rb index e047780b381..444590b1cbe 100644 --- a/spec/dependabot/metadata_finders/go/dep_spec.rb +++ b/spec/dependabot/metadata_finders/go/dep_spec.rb @@ -1,8 +1,69 @@ # frozen_string_literal: true +require "dependabot/dependency" require "dependabot/metadata_finders/go/dep" require_relative "../shared_examples_for_metadata_finders" RSpec.describe Dependabot::MetadataFinders::Go::Dep do it_behaves_like "a dependency metadata finder" + + let(:dependency) do + Dependabot::Dependency.new( + name: dependency_name, + version: "2.1.0", + requirements: requirements, + package_manager: "dep" + ) + end + let(:requirements) do + [{ + file: "Gopkg.toml", + requirement: "v2.1.0", + groups: [], + source: source + }] + end + subject(:finder) do + described_class.new(dependency: dependency, credentials: credentials) + end + let(:credentials) do + [{ + "type" => "git_source", + "host" => "github.com", + "username" => "x-access-token", + "password" => "token" + }] + end + let(:dependency_name) { "github.com/satori/go.uuid" } + let(:source) { nil } + + describe "#source_url" do + subject(:source_url) { finder.source_url } + + context "with a github name" do + it { is_expected.to eq("https://github.com/satori/go.uuid") } + + context "and no requirements" do + it { is_expected.to eq("https://github.com/satori/go.uuid") } + end + + context "that uses golang.org" do + let(:dependency_name) { "golang.org/x/text" } + it { is_expected.to eq("https://github.com/golang/text") } + end + end + + context "with a source" do + let(:source) do + { + type: "default", + source: "github.com/alias/go.uuid", + branch: nil, + ref: nil + } + end + + it { is_expected.to eq("https://github.com/alias/go.uuid") } + end + end end From 9075ff4c91091fc552b98ad6e5195bcd0b67f203 Mon Sep 17 00:00:00 2001 From: Grey Baker Date: Sat, 21 Jul 2018 23:49:31 +0100 Subject: [PATCH 05/28] Dep: Add latest_resolvable_version_with_no_unlock to UpdateChecker --- lib/dependabot/update_checkers/go/dep.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/dependabot/update_checkers/go/dep.rb b/lib/dependabot/update_checkers/go/dep.rb index f4ef1df558b..72b9d88e10a 100644 --- a/lib/dependabot/update_checkers/go/dep.rb +++ b/lib/dependabot/update_checkers/go/dep.rb @@ -17,6 +17,11 @@ def latest_resolvable_version # package manager's own resolution logic (see PHP, for example). end + def latest_resolvable_version_with_no_unlock + # Will need the same resolution logic as above, just without the + # file unlocking. + end + def updated_requirements # If the dependency file needs to be updated we store the updated # requirements on the dependency. From 1107d995247316ea7bd0e87a4b92ca178a9a8234 Mon Sep 17 00:00:00 2001 From: Grey Baker Date: Sun, 22 Jul 2018 11:01:10 +0100 Subject: [PATCH 06/28] Parse dependencies as having a git source if/when we know they do --- lib/dependabot/file_parsers/go/dep.rb | 35 ++++++++++++----- spec/dependabot/file_parsers/go/dep_spec.rb | 39 ++++++++++++++++--- .../go/gopkg_locks/unknown_source.lock | 15 +++++++ .../go/gopkg_tomls/unknown_source.toml | 4 ++ 4 files changed, 77 insertions(+), 16 deletions(-) create mode 100644 spec/fixtures/go/gopkg_locks/unknown_source.lock create mode 100644 spec/fixtures/go/gopkg_tomls/unknown_source.toml diff --git a/lib/dependabot/file_parsers/go/dep.rb b/lib/dependabot/file_parsers/go/dep.rb index 9040864c77a..8a902594b59 100644 --- a/lib/dependabot/file_parsers/go/dep.rb +++ b/lib/dependabot/file_parsers/go/dep.rb @@ -80,16 +80,31 @@ def source_from_declaration(declaration) raise "Unexpected dependency declaration: #{declaration}" end - # TODO: Figure out git sources (particularly ones which don't have - # a version) at this point, so we can use GitCommitChecker later - # Most likely looking docs: - # https://github.com/golang/dep/blob/master/vendor/github.com/Masterminds/vcs/vcs_remote_lookup.go - { - type: "default", - source: declaration["source"] || declaration["name"], - branch: declaration["branch"], - ref: declaration["revision"] - } + git_source_url = + git_source_url(declaration["source"] || declaration["name"]) + + if git_source_url + { + type: "git", + url: git_source_url, + branch: declaration["branch"], + ref: declaration["revision"] + } + else + { + type: "default", + source: declaration["source"] || declaration["name"], + branch: declaration["branch"], + ref: declaration["revision"] + } + end + end + + def git_source_url(path) + updated_path = path.gsub(%r{^golang\.org/x}, "github.com/golang") + source = Source.from_url(updated_path) + + source&.url end def parsed_file(file) diff --git a/spec/dependabot/file_parsers/go/dep_spec.rb b/spec/dependabot/file_parsers/go/dep_spec.rb index b65fde3c4c6..b2850d9c452 100644 --- a/spec/dependabot/file_parsers/go/dep_spec.rb +++ b/spec/dependabot/file_parsers/go/dep_spec.rb @@ -43,7 +43,7 @@ its(:length) { is_expected.to eq(11) } - describe "a regular dependency dependency" do + describe "a regular version dependency" do subject(:dependency) do dependencies.find { |d| d.name == "github.com/satori/go.uuid" } end @@ -58,8 +58,8 @@ file: "Gopkg.toml", groups: [], source: { - type: "default", - source: "github.com/satori/go.uuid", + type: "git", + url: "https://github.com/satori/go.uuid", branch: nil, ref: nil } @@ -68,7 +68,7 @@ end end - describe "a git dependency dependency" do + describe "a git version dependency" do subject(:dependency) do dependencies.find { |d| d.name == "golang.org/x/text" } end @@ -84,8 +84,8 @@ file: "Gopkg.toml", groups: [], source: { - type: "default", - source: "golang.org/x/text", + type: "git", + url: "https://github.com/golang/text", branch: nil, ref: "470f45bf29f4147d6fbd7dfd0a02a848e49f5bf4" } @@ -93,6 +93,33 @@ ) end end + + describe "a dependency with an unrecognised name" do + let(:manifest_fixture_name) { "unknown_source.toml" } + let(:lockfile_fixture_name) { "unknown_source.lock" } + subject(:dependency) do + dependencies.find { |d| d.name == "unknownhost.com/dgrijalva/jwt-go" } + end + + it "has the right details" do + expect(dependency).to be_a(Dependabot::Dependency) + expect(dependency.name).to eq("unknownhost.com/dgrijalva/jwt-go") + expect(dependency.version).to eq("3.2.0") + expect(dependency.requirements).to eq( + [{ + requirement: "^3.2.0", + file: "Gopkg.toml", + groups: [], + source: { + type: "default", + source: "unknownhost.com/dgrijalva/jwt-go", + branch: nil, + ref: nil + } + }] + ) + end + end end end end diff --git a/spec/fixtures/go/gopkg_locks/unknown_source.lock b/spec/fixtures/go/gopkg_locks/unknown_source.lock new file mode 100644 index 00000000000..f05d88813d7 --- /dev/null +++ b/spec/fixtures/go/gopkg_locks/unknown_source.lock @@ -0,0 +1,15 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + name = "unknownhost.com/dgrijalva/jwt-go" + packages = ["."] + revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e" + version = "v3.2.0" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "01ff2ef749623d2bc5d0d46238992ffb3385f10d53ff2345ef6898b29fbb4bfd" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/spec/fixtures/go/gopkg_tomls/unknown_source.toml b/spec/fixtures/go/gopkg_tomls/unknown_source.toml new file mode 100644 index 00000000000..d579dbdba4a --- /dev/null +++ b/spec/fixtures/go/gopkg_tomls/unknown_source.toml @@ -0,0 +1,4 @@ + +[[constraint]] + name = "unknownhost.com/dgrijalva/jwt-go" + version = "^3.2.0" From 63d5af2936cbedb780a7ffe7eaf7d5bc4839d68e Mon Sep 17 00:00:00 2001 From: Grey Baker Date: Sun, 22 Jul 2018 11:35:47 +0100 Subject: [PATCH 07/28] Dep: More file parser specs --- lib/dependabot/file_parsers/go/dep.rb | 19 ++++--- spec/dependabot/file_parsers/go/dep_spec.rb | 55 ++++++++++++++++++- spec/fixtures/go/gopkg_locks/no_version.lock | 28 ++++++++++ .../go/gopkg_locks/tag_as_revision.lock | 27 +++++++++ spec/fixtures/go/gopkg_tomls/no_version.toml | 7 +++ .../go/gopkg_tomls/tag_as_revision.toml | 8 +++ 6 files changed, 134 insertions(+), 10 deletions(-) create mode 100644 spec/fixtures/go/gopkg_locks/no_version.lock create mode 100644 spec/fixtures/go/gopkg_locks/tag_as_revision.lock create mode 100644 spec/fixtures/go/gopkg_tomls/no_version.toml create mode 100644 spec/fixtures/go/gopkg_tomls/tag_as_revision.toml diff --git a/lib/dependabot/file_parsers/go/dep.rb b/lib/dependabot/file_parsers/go/dep.rb index 8a902594b59..58308ec4577 100644 --- a/lib/dependabot/file_parsers/go/dep.rb +++ b/lib/dependabot/file_parsers/go/dep.rb @@ -80,31 +80,34 @@ def source_from_declaration(declaration) raise "Unexpected dependency declaration: #{declaration}" end - git_source_url = - git_source_url(declaration["source"] || declaration["name"]) + source = declaration["source"] || declaration["name"] - if git_source_url + git_source = git_source(source) + + if git_source && (declaration["branch"] || declaration["revision"]) { type: "git", - url: git_source_url, + url: git_source.url, branch: declaration["branch"], ref: declaration["revision"] } else { type: "default", - source: declaration["source"] || declaration["name"], + source: source, branch: declaration["branch"], ref: declaration["revision"] } end end - def git_source_url(path) + def git_source(path) updated_path = path.gsub(%r{^golang\.org/x}, "github.com/golang") - source = Source.from_url(updated_path) - source&.url + # Currently, Dependabot::Source.new will return `nil` if it can't find + # a git SCH associated with a path. If it is ever extended to handle + # non-git sources we'll need to add an additional check here. + Source.from_url(updated_path) end def parsed_file(file) diff --git a/spec/dependabot/file_parsers/go/dep_spec.rb b/spec/dependabot/file_parsers/go/dep_spec.rb index b2850d9c452..cd9a0ea3b55 100644 --- a/spec/dependabot/file_parsers/go/dep_spec.rb +++ b/spec/dependabot/file_parsers/go/dep_spec.rb @@ -58,14 +58,41 @@ file: "Gopkg.toml", groups: [], source: { - type: "git", - url: "https://github.com/satori/go.uuid", + type: "default", + source: "github.com/satori/go.uuid", branch: nil, ref: nil } }] ) end + + context "that doesn't declare a version" do + subject(:dependency) do + dependencies.find { |d| d.name == "golang.org/x/text" } + end + let(:manifest_fixture_name) { "no_version.toml" } + let(:lockfile_fixture_name) { "no_version.lock" } + + it "has the right details" do + expect(dependency).to be_a(Dependabot::Dependency) + expect(dependency.name).to eq("golang.org/x/text") + expect(dependency.version).to eq("0.3.0") + expect(dependency.requirements).to eq( + [{ + requirement: nil, + file: "Gopkg.toml", + groups: [], + source: { + type: "default", + source: "golang.org/x/text", + branch: nil, + ref: nil + } + }] + ) + end + end end describe "a git version dependency" do @@ -92,6 +119,30 @@ }] ) end + + context "that specifies a tag as its revision" do + let(:manifest_fixture_name) { "tag_as_revision.toml" } + let(:lockfile_fixture_name) { "tag_as_revision.lock" } + + it "has the right details" do + expect(dependency).to be_a(Dependabot::Dependency) + expect(dependency.name).to eq("golang.org/x/text") + expect(dependency.version).to eq("v0.3.0") + expect(dependency.requirements).to eq( + [{ + requirement: nil, + file: "Gopkg.toml", + groups: [], + source: { + type: "git", + url: "https://github.com/golang/text", + branch: nil, + ref: "v0.3.0" + } + }] + ) + end + end end describe "a dependency with an unrecognised name" do diff --git a/spec/fixtures/go/gopkg_locks/no_version.lock b/spec/fixtures/go/gopkg_locks/no_version.lock new file mode 100644 index 00000000000..b47d5c63e4a --- /dev/null +++ b/spec/fixtures/go/gopkg_locks/no_version.lock @@ -0,0 +1,28 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + name = "golang.org/x/text" + packages = [ + ".", + "collate", + "collate/build", + "internal/colltab", + "internal/gen", + "internal/tag", + "internal/triegen", + "internal/ucd", + "language", + "transform", + "unicode/cldr", + "unicode/norm" + ] + revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" + version = "v0.3.0" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "ecaba8014947bc355742bfec162054f40eaabe5e7451ede8df52256d93bcbceb" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/spec/fixtures/go/gopkg_locks/tag_as_revision.lock b/spec/fixtures/go/gopkg_locks/tag_as_revision.lock new file mode 100644 index 00000000000..9f6dccfb523 --- /dev/null +++ b/spec/fixtures/go/gopkg_locks/tag_as_revision.lock @@ -0,0 +1,27 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + name = "golang.org/x/text" + packages = [ + ".", + "collate", + "collate/build", + "internal/colltab", + "internal/gen", + "internal/tag", + "internal/triegen", + "internal/ucd", + "language", + "transform", + "unicode/cldr", + "unicode/norm" + ] + revision = "v0.3.0" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "2405704ac7a0b793021a0a72d2ff705b4a2c503b07b1e50382eecf14e46f2012" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/spec/fixtures/go/gopkg_tomls/no_version.toml b/spec/fixtures/go/gopkg_tomls/no_version.toml new file mode 100644 index 00000000000..52e1eb0ca92 --- /dev/null +++ b/spec/fixtures/go/gopkg_tomls/no_version.toml @@ -0,0 +1,7 @@ +required = [ + "golang.org/x/text", + ] + +# The collation tables must never change. +[[constraint]] + name = "golang.org/x/text" diff --git a/spec/fixtures/go/gopkg_tomls/tag_as_revision.toml b/spec/fixtures/go/gopkg_tomls/tag_as_revision.toml new file mode 100644 index 00000000000..c1a178d00c6 --- /dev/null +++ b/spec/fixtures/go/gopkg_tomls/tag_as_revision.toml @@ -0,0 +1,8 @@ +required = [ + "golang.org/x/text", + ] + +# The collation tables must never change. +[[constraint]] + name = "golang.org/x/text" + revision = "v0.3.0" From 3069ca117dcbeeaa8051314af3d95e229fd5e5e0 Mon Sep 17 00:00:00 2001 From: Grey Baker Date: Sun, 22 Jul 2018 12:27:33 +0100 Subject: [PATCH 08/28] Dep: First version of UpdateCheckers::Go::Dep#latest_version --- lib/dependabot/file_parsers/go/dep.rb | 4 +- lib/dependabot/update_checkers/go/dep.rb | 138 +++++++++++++++++- spec/dependabot/file_parsers/go/dep_spec.rb | 12 +- .../dependabot/update_checkers/go/dep_spec.rb | 69 +++++++++ spec/fixtures/git/upload_packs/text | Bin 0 -> 105319 bytes 5 files changed, 210 insertions(+), 13 deletions(-) create mode 100644 spec/fixtures/git/upload_packs/text diff --git a/lib/dependabot/file_parsers/go/dep.rb b/lib/dependabot/file_parsers/go/dep.rb index 58308ec4577..284cdb00c33 100644 --- a/lib/dependabot/file_parsers/go/dep.rb +++ b/lib/dependabot/file_parsers/go/dep.rb @@ -94,9 +94,7 @@ def source_from_declaration(declaration) else { type: "default", - source: source, - branch: declaration["branch"], - ref: declaration["revision"] + source: source } end end diff --git a/lib/dependabot/update_checkers/go/dep.rb b/lib/dependabot/update_checkers/go/dep.rb index 72b9d88e10a..3f7b88052c6 100644 --- a/lib/dependabot/update_checkers/go/dep.rb +++ b/lib/dependabot/update_checkers/go/dep.rb @@ -1,13 +1,20 @@ # frozen_string_literal: true +require "toml-rb" + +require "dependabot/source" require "dependabot/update_checkers/base" +require "dependabot/git_commit_checker" module Dependabot module UpdateCheckers module Go class Dep < Dependabot::UpdateCheckers::Base def latest_version - # Hit the registry for this dependency and get its latest version + @latest_version ||= + if git_dependency? then latest_version_for_git_dependency + else latest_release_tag_version + end end def latest_resolvable_version @@ -38,6 +45,135 @@ def latest_version_resolvable_with_full_unlock? def updated_dependencies_after_full_unlock raise NotImplementedError end + + def latest_release_tag_version + if @latest_release_tag_lookup_attempted + return @latest_release_tag_version + end + + @latest_release_tag_lookup_attempted = true + + latest_release_version_str = fetch_latest_release_tag&.sub(/^v?/, "") + return unless latest_release_version_str + return unless version_class.correct?(latest_release_version_str) + + @latest_release_tag_version = + version_class.new(latest_release_version_str) + end + + def fetch_latest_release_tag + # If this is a git dependency then getting the latest tag is trivial + if git_dependency? + return git_commit_checker.local_tag_for_latest_version&.fetch(:tag) + end + + # If not, we need to find the URL for the source code. + path = dependency.requirements. + map { |r| r.dig(:source, :source) }. + compact.first + return unless path + + updated_path = path.gsub(%r{^golang\.org/x}, "github.com/golang") + # Currently, Dependabot::Source.new will return `nil` if it can't find + # a git SCH associated with a path. If it is ever extended to handle + # non-git sources we'll need to add an additional check here. + source = Source.from_url(updated_path) + return unless source + + # Given a source, we want to find the latest tag. Piggy-back off the + # logic in GitCommitChecker to do so. + git_dep = Dependency.new( + name: dependency.name, + version: dependency.version, + requirements: [{ + file: "Gopkg.toml", + groups: [], + requirement: nil, + source: { type: "git", url: source.url } + }], + package_manager: dependency.package_manager + ) + + GitCommitChecker. + new(dependency: git_dep, credentials: credentials). + local_tag_for_latest_version&.fetch(:tag) + end + + def latest_version_for_git_dependency + latest_release = latest_release_tag_version + + # If there's been a release that includes the current pinned ref or + # that the current branch is behind, we switch to that release. + return latest_release if git_branch_or_ref_in_release?(latest_release) + + # Otherwise, if the gem isn't pinned, the latest version is just the + # latest commit for the specified branch. + unless git_commit_checker.pinned? + return git_commit_checker.head_commit_for_current_branch + end + + # If the dependency is pinned to a tag that looks like a version then + # we want to update that tag. The latest version will be the tag name + # (NOT the tag SHA, unlike in other package managers). + if git_commit_checker.pinned_ref_looks_like_version? + latest_tag = git_commit_checker.local_tag_for_latest_version + return latest_tag&.fetch(:tag) + end + + # If the dependency is pinned to a tag that doesn't look like a + # version then there's nothing we can do. + nil + end + + def git_branch_or_ref_in_release?(release) + return false unless release + git_commit_checker.branch_or_ref_in_release?(release) + end + + def dependencies_to_import + # There's no way to tell whether dependencies that appear in the + # lockfile are there because they're imported themselves or because + # they're sub-dependencies of something else. v0.5.0 will fix that + # problem, but for now we just have to import everything. + # + # NOTE: This means the `inputs-digest` we generate will be wrong. + # That's a pity, but we'd have to iterate through too many + # possibilities to get it right. Again, this is fixed in v0.5.0. + parsed_file(lockfile).fetch("required").map do |detail| + detail["name"] + end + end + + def git_dependency? + git_commit_checker.git_dependency? + end + + def git_commit_checker + @git_commit_checker ||= + GitCommitChecker.new( + dependency: dependency, + credentials: credentials + ) + end + + def parsed_file(file) + @parsed_file ||= {} + @parsed_file[file.name] ||= TomlRB.parse(file.content) + rescue TomlRB::ParseError + raise Dependabot::DependencyFileNotParseable, file.path + end + + def manifest + @manifest ||= dependency_files.find { |f| f.name == "Gopkg.toml" } + raise "No Gopkg.lock!" unless @manifest + @manifest + end + + def lockfile + @lockfile = dependency_files.find { |f| f.name == "Gopkg.lock" } + raise "No Gopkg.lock!" unless @lockfile + @lockfile + end end end end diff --git a/spec/dependabot/file_parsers/go/dep_spec.rb b/spec/dependabot/file_parsers/go/dep_spec.rb index cd9a0ea3b55..16caea9da18 100644 --- a/spec/dependabot/file_parsers/go/dep_spec.rb +++ b/spec/dependabot/file_parsers/go/dep_spec.rb @@ -59,9 +59,7 @@ groups: [], source: { type: "default", - source: "github.com/satori/go.uuid", - branch: nil, - ref: nil + source: "github.com/satori/go.uuid" } }] ) @@ -85,9 +83,7 @@ groups: [], source: { type: "default", - source: "golang.org/x/text", - branch: nil, - ref: nil + source: "golang.org/x/text" } }] ) @@ -163,9 +159,7 @@ groups: [], source: { type: "default", - source: "unknownhost.com/dgrijalva/jwt-go", - branch: nil, - ref: nil + source: "unknownhost.com/dgrijalva/jwt-go" } }] ) diff --git a/spec/dependabot/update_checkers/go/dep_spec.rb b/spec/dependabot/update_checkers/go/dep_spec.rb index 86ad634fb16..37932cd6049 100644 --- a/spec/dependabot/update_checkers/go/dep_spec.rb +++ b/spec/dependabot/update_checkers/go/dep_spec.rb @@ -5,4 +5,73 @@ RSpec.describe Dependabot::UpdateCheckers::Go::Dep do it_behaves_like "an update checker" + + let(:checker) do + described_class.new( + dependency: dependency, + dependency_files: dependency_files, + credentials: credentials, + ignored_versions: ignored_versions + ) + end + + let(:ignored_versions) { [] } + let(:credentials) do + [{ + "type" => "git_source", + "host" => "github.com", + "username" => "x-access-token", + "password" => "token" + }] + end + let(:dependency_files) do + [ + Dependabot::DependencyFile.new( + name: "Gopkg.toml", + content: fixture("go", "gopkg_tomls", manifest_fixture_name) + ), + Dependabot::DependencyFile.new( + name: "Gopkg.lock", + content: fixture("go", "gopkg_locks", lockfile_fixture_name) + ) + ] + end + let(:manifest_fixture_name) { "no_version.toml" } + let(:lockfile_fixture_name) { "no_version.lock" } + let(:dependency) do + Dependabot::Dependency.new( + name: dependency_name, + version: dependency_version, + requirements: requirements, + package_manager: "dep" + ) + end + let(:requirements) do + [{ file: "Gopkg.toml", requirement: req_str, groups: [], source: source }] + end + let(:dependency_name) { "golang.org/x/text" } + let(:dependency_version) { "0.2.0" } + let(:req_str) { nil } + let(:source) { { type: "default", source: "golang.org/x/text" } } + + let(:service_pack_url) do + "https://github.com/golang/text.git/info/refs"\ + "?service=git-upload-pack" + end + before do + stub_request(:get, service_pack_url). + to_return( + status: 200, + body: fixture("git", "upload_packs", upload_pack_fixture), + headers: { + "content-type" => "application/x-git-upload-pack-advertisement" + } + ) + end + let(:upload_pack_fixture) { "text" } + + describe "#latest_version" do + subject { checker.latest_version } + it { is_expected.to eq(Gem::Version.new("0.3.0")) } + end end diff --git a/spec/fixtures/git/upload_packs/text b/spec/fixtures/git/upload_packs/text new file mode 100644 index 0000000000000000000000000000000000000000..ba8fa5d1db139b036d3c901ce5edae5ac12637f2 GIT binary patch literal 105319 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 Nv4#{xKG55@{~z5sBcuQT literal 0 HcmV?d00001 From 62360b980b4320b44c2997cae1223460d6b1585e Mon Sep 17 00:00:00 2001 From: Grey Baker Date: Sun, 22 Jul 2018 13:39:44 +0100 Subject: [PATCH 09/28] Dep: More tests for UpdateCheckers::Go::Dep#latest_version --- .../dependabot/update_checkers/go/dep_spec.rb | 91 +++++++++ .../github/commit_compare_identical.json | 175 ++++++++++++++++++ spec/fixtures/go/gopkg_locks/branch.lock | 30 +++ spec/fixtures/go/gopkg_tomls/branch.toml | 8 + 4 files changed, 304 insertions(+) create mode 100644 spec/fixtures/github/commit_compare_identical.json create mode 100644 spec/fixtures/go/gopkg_locks/branch.lock create mode 100644 spec/fixtures/go/gopkg_tomls/branch.toml diff --git a/spec/dependabot/update_checkers/go/dep_spec.rb b/spec/dependabot/update_checkers/go/dep_spec.rb index 37932cd6049..621871c7893 100644 --- a/spec/dependabot/update_checkers/go/dep_spec.rb +++ b/spec/dependabot/update_checkers/go/dep_spec.rb @@ -73,5 +73,96 @@ describe "#latest_version" do subject { checker.latest_version } it { is_expected.to eq(Gem::Version.new("0.3.0")) } + + context "with a git source" do + context "that specifies a branch" do + let(:manifest_fixture_name) { "branch.toml" } + let(:lockfile_fixture_name) { "branch.lock" } + + let(:source) do + { + type: "git", + url: "https://github.com/golang/text", + branch: "master", + ref: nil + } + end + + before do + repo_url = "https://api.github.com/repos/golang/text" + stub_request(:get, repo_url + "/compare/v0.3.0...master"). + to_return( + status: 200, + body: commit_compare_response, + headers: { "Content-Type" => "application/json" } + ) + end + + context "that is behind the latest release" do + let(:commit_compare_response) do + fixture("github", "commit_compare_behind.json") + end + + it { is_expected.to eq(Gem::Version.new("0.3.0")) } + end + + context "that is diverged from the latest release" do + let(:commit_compare_response) do + fixture("github", "commit_compare_diverged.json") + end + + it { is_expected.to eq("0605a8320aceb4207a5fb3521281e17ec2075476") } + end + end + + context "that specifies a tag" do + let(:manifest_fixture_name) { "tag_as_revision.toml" } + let(:lockfile_fixture_name) { "tag_as_revision.lock" } + + let(:source) do + { + type: "git", + url: "https://github.com/golang/text", + branch: nil, + ref: "v0.2.0" + } + end + + before do + repo_url = "https://api.github.com/repos/golang/text" + stub_request(:get, repo_url + "/compare/v0.3.0...v0.2.0"). + to_return( + status: 200, + body: commit_compare_response, + headers: { "Content-Type" => "application/json" } + ) + end + let(:commit_compare_response) do + fixture("github", "commit_compare_behind.json") + end + + it { is_expected.to eq(Gem::Version.new("0.3.0")) } + + context "that is up-to-date" do + let(:commit_compare_response) do + fixture("github", "commit_compare_identical.json") + end + + # Still make an update as we wish to switch source declaration style. + # (Could decide not to do this if it causes trouble.) + it { is_expected.to eq(Gem::Version.new("0.3.0")) } + end + + context "when the new version isn't a direct update to the old one" do + let(:commit_compare_response) do + fixture("github", "commit_compare_diverged.json") + end + + # Still make an update as we wish to switch source declaration style. + # (Could decide not to do this if it causes trouble.) + it { is_expected.to eq("v0.3.0") } + end + end + end end end diff --git a/spec/fixtures/github/commit_compare_identical.json b/spec/fixtures/github/commit_compare_identical.json new file mode 100644 index 00000000000..38dfe81fb89 --- /dev/null +++ b/spec/fixtures/github/commit_compare_identical.json @@ -0,0 +1,175 @@ +{ + "url": "https://api.github.com/repos/golang/text/compare/v0.3.0...v0.3.0", + "html_url": "https://github.com/golang/text/compare/v0.3.0...v0.3.0", + "permalink_url": "https://github.com/golang/text/compare/golang:f21a4df...golang:f21a4df", + "diff_url": "https://github.com/golang/text/compare/v0.3.0...v0.3.0.diff", + "patch_url": "https://github.com/golang/text/compare/v0.3.0...v0.3.0.patch", + "base_commit": { + "sha": "f21a4dfb5e38f5895301dc265a8def02365cc3d0", + "node_id": "MDY6Q29tbWl0Mjc1MjIyOTU6ZjIxYTRkZmI1ZTM4ZjU4OTUzMDFkYzI2NWE4ZGVmMDIzNjVjYzNkMA==", + "commit": { + "author": { + "name": "Marcel van Lohuizen", + "email": "mpvl@golang.org", + "date": "2017-12-09T17:05:50Z" + }, + "committer": { + "name": "Marcel van Lohuizen", + "email": "mpvl@golang.org", + "date": "2017-12-14T13:08:43Z" + }, + "message": "all: upgrade to CLDR 32\n\nChange-Id: I80ba02e97f8c9a6bc364cf8679b90aee13a1ce23\nReviewed-on: https://go-review.googlesource.com/82905\nRun-TryBot: Marcel van Lohuizen \nTryBot-Result: Gobot Gobot \nReviewed-by: Nigel Tao ", + "tree": { + "sha": "d495a1fe53fe3fd1794be36b4a4c8d835254db7b", + "url": "https://api.github.com/repos/golang/text/git/trees/d495a1fe53fe3fd1794be36b4a4c8d835254db7b" + }, + "url": "https://api.github.com/repos/golang/text/git/commits/f21a4dfb5e38f5895301dc265a8def02365cc3d0", + "comment_count": 0, + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null + } + }, + "url": "https://api.github.com/repos/golang/text/commits/f21a4dfb5e38f5895301dc265a8def02365cc3d0", + "html_url": "https://github.com/golang/text/commit/f21a4dfb5e38f5895301dc265a8def02365cc3d0", + "comments_url": "https://api.github.com/repos/golang/text/commits/f21a4dfb5e38f5895301dc265a8def02365cc3d0/comments", + "author": { + "login": "mpvl", + "id": 6445383, + "node_id": "MDQ6VXNlcjY0NDUzODM=", + "avatar_url": "https://avatars2.githubusercontent.com/u/6445383?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/mpvl", + "html_url": "https://github.com/mpvl", + "followers_url": "https://api.github.com/users/mpvl/followers", + "following_url": "https://api.github.com/users/mpvl/following{/other_user}", + "gists_url": "https://api.github.com/users/mpvl/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mpvl/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mpvl/subscriptions", + "organizations_url": "https://api.github.com/users/mpvl/orgs", + "repos_url": "https://api.github.com/users/mpvl/repos", + "events_url": "https://api.github.com/users/mpvl/events{/privacy}", + "received_events_url": "https://api.github.com/users/mpvl/received_events", + "type": "User", + "site_admin": false + }, + "committer": { + "login": "mpvl", + "id": 6445383, + "node_id": "MDQ6VXNlcjY0NDUzODM=", + "avatar_url": "https://avatars2.githubusercontent.com/u/6445383?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/mpvl", + "html_url": "https://github.com/mpvl", + "followers_url": "https://api.github.com/users/mpvl/followers", + "following_url": "https://api.github.com/users/mpvl/following{/other_user}", + "gists_url": "https://api.github.com/users/mpvl/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mpvl/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mpvl/subscriptions", + "organizations_url": "https://api.github.com/users/mpvl/orgs", + "repos_url": "https://api.github.com/users/mpvl/repos", + "events_url": "https://api.github.com/users/mpvl/events{/privacy}", + "received_events_url": "https://api.github.com/users/mpvl/received_events", + "type": "User", + "site_admin": false + }, + "parents": [ + { + "sha": "d5f0155fc124972d018bea023348f2bd9e1d341e", + "url": "https://api.github.com/repos/golang/text/commits/d5f0155fc124972d018bea023348f2bd9e1d341e", + "html_url": "https://github.com/golang/text/commit/d5f0155fc124972d018bea023348f2bd9e1d341e" + } + ] + }, + "merge_base_commit": { + "sha": "f21a4dfb5e38f5895301dc265a8def02365cc3d0", + "node_id": "MDY6Q29tbWl0Mjc1MjIyOTU6ZjIxYTRkZmI1ZTM4ZjU4OTUzMDFkYzI2NWE4ZGVmMDIzNjVjYzNkMA==", + "commit": { + "author": { + "name": "Marcel van Lohuizen", + "email": "mpvl@golang.org", + "date": "2017-12-09T17:05:50Z" + }, + "committer": { + "name": "Marcel van Lohuizen", + "email": "mpvl@golang.org", + "date": "2017-12-14T13:08:43Z" + }, + "message": "all: upgrade to CLDR 32\n\nChange-Id: I80ba02e97f8c9a6bc364cf8679b90aee13a1ce23\nReviewed-on: https://go-review.googlesource.com/82905\nRun-TryBot: Marcel van Lohuizen \nTryBot-Result: Gobot Gobot \nReviewed-by: Nigel Tao ", + "tree": { + "sha": "d495a1fe53fe3fd1794be36b4a4c8d835254db7b", + "url": "https://api.github.com/repos/golang/text/git/trees/d495a1fe53fe3fd1794be36b4a4c8d835254db7b" + }, + "url": "https://api.github.com/repos/golang/text/git/commits/f21a4dfb5e38f5895301dc265a8def02365cc3d0", + "comment_count": 0, + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null + } + }, + "url": "https://api.github.com/repos/golang/text/commits/f21a4dfb5e38f5895301dc265a8def02365cc3d0", + "html_url": "https://github.com/golang/text/commit/f21a4dfb5e38f5895301dc265a8def02365cc3d0", + "comments_url": "https://api.github.com/repos/golang/text/commits/f21a4dfb5e38f5895301dc265a8def02365cc3d0/comments", + "author": { + "login": "mpvl", + "id": 6445383, + "node_id": "MDQ6VXNlcjY0NDUzODM=", + "avatar_url": "https://avatars2.githubusercontent.com/u/6445383?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/mpvl", + "html_url": "https://github.com/mpvl", + "followers_url": "https://api.github.com/users/mpvl/followers", + "following_url": "https://api.github.com/users/mpvl/following{/other_user}", + "gists_url": "https://api.github.com/users/mpvl/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mpvl/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mpvl/subscriptions", + "organizations_url": "https://api.github.com/users/mpvl/orgs", + "repos_url": "https://api.github.com/users/mpvl/repos", + "events_url": "https://api.github.com/users/mpvl/events{/privacy}", + "received_events_url": "https://api.github.com/users/mpvl/received_events", + "type": "User", + "site_admin": false + }, + "committer": { + "login": "mpvl", + "id": 6445383, + "node_id": "MDQ6VXNlcjY0NDUzODM=", + "avatar_url": "https://avatars2.githubusercontent.com/u/6445383?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/mpvl", + "html_url": "https://github.com/mpvl", + "followers_url": "https://api.github.com/users/mpvl/followers", + "following_url": "https://api.github.com/users/mpvl/following{/other_user}", + "gists_url": "https://api.github.com/users/mpvl/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mpvl/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mpvl/subscriptions", + "organizations_url": "https://api.github.com/users/mpvl/orgs", + "repos_url": "https://api.github.com/users/mpvl/repos", + "events_url": "https://api.github.com/users/mpvl/events{/privacy}", + "received_events_url": "https://api.github.com/users/mpvl/received_events", + "type": "User", + "site_admin": false + }, + "parents": [ + { + "sha": "d5f0155fc124972d018bea023348f2bd9e1d341e", + "url": "https://api.github.com/repos/golang/text/commits/d5f0155fc124972d018bea023348f2bd9e1d341e", + "html_url": "https://github.com/golang/text/commit/d5f0155fc124972d018bea023348f2bd9e1d341e" + } + ] + }, + "status": "identical", + "ahead_by": 0, + "behind_by": 0, + "total_commits": 0, + "commits": [ + + ], + "files": [ + + ] +} diff --git a/spec/fixtures/go/gopkg_locks/branch.lock b/spec/fixtures/go/gopkg_locks/branch.lock new file mode 100644 index 00000000000..e44d91340bf --- /dev/null +++ b/spec/fixtures/go/gopkg_locks/branch.lock @@ -0,0 +1,30 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + branch = "master" + name = "golang.org/x/text" + packages = [ + ".", + "collate", + "collate/build", + "internal/colltab", + "internal/gen", + "internal/language", + "internal/language/compact", + "internal/tag", + "internal/triegen", + "internal/ucd", + "language", + "transform", + "unicode/cldr", + "unicode/norm" + ] + revision = "7dd2c8130f5e924233f5543598300651c386d431" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "405bb493c54786c5c6ad50e065d9d815af21e413be8ef1b592ca5333d8144eb5" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/spec/fixtures/go/gopkg_tomls/branch.toml b/spec/fixtures/go/gopkg_tomls/branch.toml new file mode 100644 index 00000000000..86bb1d26fb4 --- /dev/null +++ b/spec/fixtures/go/gopkg_tomls/branch.toml @@ -0,0 +1,8 @@ +required = [ + "golang.org/x/text", + ] + +# The collation tables must never change. +[[constraint]] + name = "golang.org/x/text" + branch = "master" From aa7796213f307795006fa04d07b12aed181d2a0a Mon Sep 17 00:00:00 2001 From: Grey Baker Date: Sun, 22 Jul 2018 13:49:12 +0100 Subject: [PATCH 10/28] Add ignored versions support to GitCommitChecker --- lib/dependabot/git_commit_checker.rb | 26 +++++++++++++++---- lib/dependabot/update_checkers/go/dep.rb | 5 ++-- spec/dependabot/git_commit_checker_spec.rb | 9 ++++++- .../dependabot/update_checkers/go/dep_spec.rb | 5 ++++ 4 files changed, 37 insertions(+), 8 deletions(-) diff --git a/lib/dependabot/git_commit_checker.rb b/lib/dependabot/git_commit_checker.rb index 70430312423..9f8341e5b02 100644 --- a/lib/dependabot/git_commit_checker.rb +++ b/lib/dependabot/git_commit_checker.rb @@ -8,14 +8,16 @@ require "dependabot/utils" require "dependabot/source" +# rubocop:disable Metrics/ClassLength module Dependabot class GitCommitChecker VERSION_REGEX = /(?[0-9]+\.[0-9]+(?:\.[a-zA-Z0-9\-]+)*)$/ KNOWN_HOSTS = /github\.com|bitbucket\.org|gitlab.com/ - def initialize(dependency:, credentials:) + def initialize(dependency:, credentials:, ignored_versions: []) @dependency = dependency @credentials = credentials + @ignored_versions = ignored_versions end def git_dependency? @@ -58,14 +60,19 @@ def head_commit_for_current_branch end def local_tag_for_latest_version - tag = + tags = local_tags. select { |t| t.name.match?(VERSION_REGEX) }. - max_by do |t| + reject do |t| version = t.name.match(VERSION_REGEX).named_captures.fetch("version") - version_class.new(version) + ignore_reqs.any? { |r| r.satisfied_by?(version_class.new(version)) } end + tag = tags.max_by do |t| + version = t.name.match(VERSION_REGEX).named_captures.fetch("version") + version_class.new(version) + end + return unless tag { tag: tag.name, @@ -76,7 +83,7 @@ def local_tag_for_latest_version private - attr_reader :dependency, :credentials + attr_reader :dependency, :credentials, :ignored_versions def pinned_ref_in_release?(version) raise "Not a git dependency!" unless git_dependency? @@ -322,12 +329,21 @@ def listing_upload_pack @listing_upload_pack ||= fetch_upload_pack_for(listing_source_url) end + def ignore_reqs + ignored_versions.map { |req| requirement_class.new(req.split(",")) } + end + def version_class Utils.version_class_for_package_manager(dependency.package_manager) end + def requirement_class + Utils.requirement_class_for_package_manager(dependency.package_manager) + end + def sha_for_update_pack_line(line) line.split(" ").first.chars.last(40).join end end end +# rubocop:enable Metrics/ClassLength diff --git a/lib/dependabot/update_checkers/go/dep.rb b/lib/dependabot/update_checkers/go/dep.rb index 3f7b88052c6..3a614a16aa2 100644 --- a/lib/dependabot/update_checkers/go/dep.rb +++ b/lib/dependabot/update_checkers/go/dep.rb @@ -71,7 +71,7 @@ def fetch_latest_release_tag path = dependency.requirements. map { |r| r.dig(:source, :source) }. compact.first - return unless path + path ||= dependency.name updated_path = path.gsub(%r{^golang\.org/x}, "github.com/golang") # Currently, Dependabot::Source.new will return `nil` if it can't find @@ -152,7 +152,8 @@ def git_commit_checker @git_commit_checker ||= GitCommitChecker.new( dependency: dependency, - credentials: credentials + credentials: credentials, + ignored_versions: ignored_versions ) end diff --git a/spec/dependabot/git_commit_checker_spec.rb b/spec/dependabot/git_commit_checker_spec.rb index 91da12ab8e6..b1a7bb3c630 100644 --- a/spec/dependabot/git_commit_checker_spec.rb +++ b/spec/dependabot/git_commit_checker_spec.rb @@ -8,7 +8,8 @@ let(:checker) do described_class.new( dependency: dependency, - credentials: credentials + credentials: credentials, + ignored_versions: ignored_versions ) end @@ -20,6 +21,7 @@ package_manager: "bundler" ) end + let(:ignored_versions) { [] } let(:requirements) do [{ file: "Gemfile", requirement: ">= 0", groups: [], source: source }] @@ -703,6 +705,11 @@ its([:tag_sha]) do is_expected.to eq("37f41032a0f191507903ebbae8a5c0cb945d7585") end + + context "and an ignore condition" do + let(:ignored_versions) { [">= 1.12.0"] } + its([:tag]) { is_expected.to eq("v1.11.1") } + end end end end diff --git a/spec/dependabot/update_checkers/go/dep_spec.rb b/spec/dependabot/update_checkers/go/dep_spec.rb index 621871c7893..5539ea52bf6 100644 --- a/spec/dependabot/update_checkers/go/dep_spec.rb +++ b/spec/dependabot/update_checkers/go/dep_spec.rb @@ -74,6 +74,11 @@ subject { checker.latest_version } it { is_expected.to eq(Gem::Version.new("0.3.0")) } + context "with a sub-dependency" do + let(:requirements) { [] } + it { is_expected.to eq(Gem::Version.new("0.3.0")) } + end + context "with a git source" do context "that specifies a branch" do let(:manifest_fixture_name) { "branch.toml" } From febf2dc4d7eb310c1a250641765c1a2c2ba0833d Mon Sep 17 00:00:00 2001 From: Grey Baker Date: Sun, 22 Jul 2018 14:30:32 +0100 Subject: [PATCH 11/28] Dep: Move UpdateCheckers::Go::Dep#latest_version logic into separate class --- lib/dependabot/update_checkers/go/dep.rb | 112 +----------- .../go/dep/latest_version_finder.rb | 171 ++++++++++++++++++ .../go/dep/latest_version_finder_spec.rb | 170 +++++++++++++++++ .../dependabot/update_checkers/go/dep_spec.rb | 102 +---------- 4 files changed, 357 insertions(+), 198 deletions(-) create mode 100644 lib/dependabot/update_checkers/go/dep/latest_version_finder.rb create mode 100644 spec/dependabot/update_checkers/go/dep/latest_version_finder_spec.rb diff --git a/lib/dependabot/update_checkers/go/dep.rb b/lib/dependabot/update_checkers/go/dep.rb index 3a614a16aa2..25f1062cd0d 100644 --- a/lib/dependabot/update_checkers/go/dep.rb +++ b/lib/dependabot/update_checkers/go/dep.rb @@ -1,20 +1,21 @@ # frozen_string_literal: true -require "toml-rb" - -require "dependabot/source" require "dependabot/update_checkers/base" -require "dependabot/git_commit_checker" module Dependabot module UpdateCheckers module Go class Dep < Dependabot::UpdateCheckers::Base + require_relative "dep/latest_version_finder" + def latest_version @latest_version ||= - if git_dependency? then latest_version_for_git_dependency - else latest_release_tag_version - end + LatestVersionFinder.new( + dependency: dependency, + dependency_files: dependency_files, + credentials: credentials, + ignored_versions: ignored_versions + ).latest_version end def latest_resolvable_version @@ -46,90 +47,6 @@ def updated_dependencies_after_full_unlock raise NotImplementedError end - def latest_release_tag_version - if @latest_release_tag_lookup_attempted - return @latest_release_tag_version - end - - @latest_release_tag_lookup_attempted = true - - latest_release_version_str = fetch_latest_release_tag&.sub(/^v?/, "") - return unless latest_release_version_str - return unless version_class.correct?(latest_release_version_str) - - @latest_release_tag_version = - version_class.new(latest_release_version_str) - end - - def fetch_latest_release_tag - # If this is a git dependency then getting the latest tag is trivial - if git_dependency? - return git_commit_checker.local_tag_for_latest_version&.fetch(:tag) - end - - # If not, we need to find the URL for the source code. - path = dependency.requirements. - map { |r| r.dig(:source, :source) }. - compact.first - path ||= dependency.name - - updated_path = path.gsub(%r{^golang\.org/x}, "github.com/golang") - # Currently, Dependabot::Source.new will return `nil` if it can't find - # a git SCH associated with a path. If it is ever extended to handle - # non-git sources we'll need to add an additional check here. - source = Source.from_url(updated_path) - return unless source - - # Given a source, we want to find the latest tag. Piggy-back off the - # logic in GitCommitChecker to do so. - git_dep = Dependency.new( - name: dependency.name, - version: dependency.version, - requirements: [{ - file: "Gopkg.toml", - groups: [], - requirement: nil, - source: { type: "git", url: source.url } - }], - package_manager: dependency.package_manager - ) - - GitCommitChecker. - new(dependency: git_dep, credentials: credentials). - local_tag_for_latest_version&.fetch(:tag) - end - - def latest_version_for_git_dependency - latest_release = latest_release_tag_version - - # If there's been a release that includes the current pinned ref or - # that the current branch is behind, we switch to that release. - return latest_release if git_branch_or_ref_in_release?(latest_release) - - # Otherwise, if the gem isn't pinned, the latest version is just the - # latest commit for the specified branch. - unless git_commit_checker.pinned? - return git_commit_checker.head_commit_for_current_branch - end - - # If the dependency is pinned to a tag that looks like a version then - # we want to update that tag. The latest version will be the tag name - # (NOT the tag SHA, unlike in other package managers). - if git_commit_checker.pinned_ref_looks_like_version? - latest_tag = git_commit_checker.local_tag_for_latest_version - return latest_tag&.fetch(:tag) - end - - # If the dependency is pinned to a tag that doesn't look like a - # version then there's nothing we can do. - nil - end - - def git_branch_or_ref_in_release?(release) - return false unless release - git_commit_checker.branch_or_ref_in_release?(release) - end - def dependencies_to_import # There's no way to tell whether dependencies that appear in the # lockfile are there because they're imported themselves or because @@ -144,19 +61,6 @@ def dependencies_to_import end end - def git_dependency? - git_commit_checker.git_dependency? - end - - def git_commit_checker - @git_commit_checker ||= - GitCommitChecker.new( - dependency: dependency, - credentials: credentials, - ignored_versions: ignored_versions - ) - end - def parsed_file(file) @parsed_file ||= {} @parsed_file[file.name] ||= TomlRB.parse(file.content) diff --git a/lib/dependabot/update_checkers/go/dep/latest_version_finder.rb b/lib/dependabot/update_checkers/go/dep/latest_version_finder.rb new file mode 100644 index 00000000000..e9f9e52fd47 --- /dev/null +++ b/lib/dependabot/update_checkers/go/dep/latest_version_finder.rb @@ -0,0 +1,171 @@ +# frozen_string_literal: true + +require "toml-rb" + +require "dependabot/source" +require "dependabot/update_checkers/go/dep" +require "dependabot/git_commit_checker" + +module Dependabot + module UpdateCheckers + module Go + class Dep + class LatestVersionFinder + def initialize(dependency:, dependency_files:, credentials:, + ignored_versions:) + @dependency = dependency + @dependency_files = dependency_files + @credentials = credentials + @ignored_versions = ignored_versions + end + + def latest_version + @latest_version ||= + if git_dependency? then latest_version_for_git_dependency + else latest_release_tag_version + end + end + + private + + attr_reader :dependency, :dependency_files, :credentials, + :ignored_versions + + def latest_release_tag_version + if @latest_release_tag_lookup_attempted + return @latest_release_tag_version + end + + @latest_release_tag_lookup_attempted = true + + latest_release_str = fetch_latest_release_tag&.sub(/^v?/, "") + return unless latest_release_str + return unless version_class.correct?(latest_release_str) + + @latest_release_tag_version = + version_class.new(latest_release_str) + end + + def fetch_latest_release_tag + # If this is a git dependency then getting the latest tag is trivial + if git_dependency? + return git_commit_checker. + local_tag_for_latest_version&.fetch(:tag) + end + + # If not, we need to find the URL for the source code. + path = dependency.requirements. + map { |r| r.dig(:source, :source) }.compact.first + path ||= dependency.name + + updated_path = path.gsub(%r{^golang\.org/x}, "github.com/golang") + # Currently, Dependabot::Source.new will return `nil` if it can't + # find a git SCH associated with a path. If it is ever extended to + # handle non-git sources we'll need to add an additional check here. + source = Source.from_url(updated_path) + return unless source + + # Given a source, we want to find the latest tag. Piggy-back off the + # logic in GitCommitChecker to do so. + git_dep = Dependency.new( + name: dependency.name, + version: dependency.version, + requirements: [{ + file: "Gopkg.toml", + groups: [], + requirement: nil, + source: { type: "git", url: source.url } + }], + package_manager: dependency.package_manager + ) + + GitCommitChecker. + new(dependency: git_dep, credentials: credentials). + local_tag_for_latest_version&.fetch(:tag) + end + + def latest_version_for_git_dependency + latest_release = latest_release_tag_version + + # If there's been a release that includes the current pinned ref or + # that the current branch is behind, we switch to that release. + return latest_release if branch_or_ref_in_release?(latest_release) + + # Otherwise, if the gem isn't pinned, the latest version is just the + # latest commit for the specified branch. + unless git_commit_checker.pinned? + return git_commit_checker.head_commit_for_current_branch + end + + # If the dependency is pinned to a tag that looks like a version + # then we want to update that tag. The latest version will be the + # tag name (NOT the tag SHA, unlike in other package managers). + if git_commit_checker.pinned_ref_looks_like_version? + latest_tag = git_commit_checker.local_tag_for_latest_version + return latest_tag&.fetch(:tag) + end + + # If the dependency is pinned to a tag that doesn't look like a + # version then there's nothing we can do. + nil + end + + def branch_or_ref_in_release?(release) + return false unless release + git_commit_checker.branch_or_ref_in_release?(release) + end + + def dependencies_to_import + # There's no way to tell whether dependencies that appear in the + # lockfile are there because they're imported themselves or because + # they're sub-dependencies of something else. v0.5.0 will fix that + # problem, but for now we just have to import everything. + # + # NOTE: This means the `inputs-digest` we generate will be wrong. + # That's a pity, but we'd have to iterate through too many + # possibilities to get it right. Again, this is fixed in v0.5.0. + parsed_file(lockfile).fetch("required").map do |detail| + detail["name"] + end + end + + def git_dependency? + git_commit_checker.git_dependency? + end + + def git_commit_checker + @git_commit_checker ||= + GitCommitChecker.new( + dependency: dependency, + credentials: credentials, + ignored_versions: ignored_versions + ) + end + + def parsed_file(file) + @parsed_file ||= {} + @parsed_file[file.name] ||= TomlRB.parse(file.content) + rescue TomlRB::ParseError + raise Dependabot::DependencyFileNotParseable, file.path + end + + def version_class + Utils.version_class_for_package_manager(dependency.package_manager) + end + + def manifest + @manifest ||= dependency_files.find { |f| f.name == "Gopkg.toml" } + raise "No Gopkg.lock!" unless @manifest + @manifest + end + + def lockfile + @lockfile = dependency_files.find { |f| f.name == "Gopkg.lock" } + raise "No Gopkg.lock!" unless @lockfile + @lockfile + end + end + end + end + end +end diff --git a/spec/dependabot/update_checkers/go/dep/latest_version_finder_spec.rb b/spec/dependabot/update_checkers/go/dep/latest_version_finder_spec.rb new file mode 100644 index 00000000000..3e40ab42385 --- /dev/null +++ b/spec/dependabot/update_checkers/go/dep/latest_version_finder_spec.rb @@ -0,0 +1,170 @@ +# frozen_string_literal: true + +require "dependabot/update_checkers/go/dep/latest_version_finder" + +RSpec.describe Dependabot::UpdateCheckers::Go::Dep::LatestVersionFinder do + let(:finder) do + described_class.new( + dependency: dependency, + dependency_files: dependency_files, + credentials: credentials, + ignored_versions: ignored_versions + ) + end + + let(:ignored_versions) { [] } + let(:credentials) do + [{ + "type" => "git_source", + "host" => "github.com", + "username" => "x-access-token", + "password" => "token" + }] + end + let(:dependency_files) do + [ + Dependabot::DependencyFile.new( + name: "Gopkg.toml", + content: fixture("go", "gopkg_tomls", manifest_fixture_name) + ), + Dependabot::DependencyFile.new( + name: "Gopkg.lock", + content: fixture("go", "gopkg_locks", lockfile_fixture_name) + ) + ] + end + let(:manifest_fixture_name) { "no_version.toml" } + let(:lockfile_fixture_name) { "no_version.lock" } + let(:dependency) do + Dependabot::Dependency.new( + name: dependency_name, + version: dependency_version, + requirements: requirements, + package_manager: "dep" + ) + end + let(:requirements) do + [{ file: "Gopkg.toml", requirement: req_str, groups: [], source: source }] + end + let(:dependency_name) { "golang.org/x/text" } + let(:dependency_version) { "0.2.0" } + let(:req_str) { nil } + let(:source) { { type: "default", source: "golang.org/x/text" } } + + let(:service_pack_url) do + "https://github.com/golang/text.git/info/refs"\ + "?service=git-upload-pack" + end + before do + stub_request(:get, service_pack_url). + to_return( + status: 200, + body: fixture("git", "upload_packs", upload_pack_fixture), + headers: { + "content-type" => "application/x-git-upload-pack-advertisement" + } + ) + end + let(:upload_pack_fixture) { "text" } + + describe "#latest_version" do + subject { finder.latest_version } + it { is_expected.to eq(Gem::Version.new("0.3.0")) } + + context "with a sub-dependency" do + let(:requirements) { [] } + it { is_expected.to eq(Gem::Version.new("0.3.0")) } + end + + context "with a git source" do + context "that specifies a branch" do + let(:manifest_fixture_name) { "branch.toml" } + let(:lockfile_fixture_name) { "branch.lock" } + + let(:source) do + { + type: "git", + url: "https://github.com/golang/text", + branch: "master", + ref: nil + } + end + + before do + repo_url = "https://api.github.com/repos/golang/text" + stub_request(:get, repo_url + "/compare/v0.3.0...master"). + to_return( + status: 200, + body: commit_compare_response, + headers: { "Content-Type" => "application/json" } + ) + end + + context "that is behind the latest release" do + let(:commit_compare_response) do + fixture("github", "commit_compare_behind.json") + end + + it { is_expected.to eq(Gem::Version.new("0.3.0")) } + end + + context "that is diverged from the latest release" do + let(:commit_compare_response) do + fixture("github", "commit_compare_diverged.json") + end + + it { is_expected.to eq("0605a8320aceb4207a5fb3521281e17ec2075476") } + end + end + + context "that specifies a tag" do + let(:manifest_fixture_name) { "tag_as_revision.toml" } + let(:lockfile_fixture_name) { "tag_as_revision.lock" } + + let(:source) do + { + type: "git", + url: "https://github.com/golang/text", + branch: nil, + ref: "v0.2.0" + } + end + + before do + repo_url = "https://api.github.com/repos/golang/text" + stub_request(:get, repo_url + "/compare/v0.3.0...v0.2.0"). + to_return( + status: 200, + body: commit_compare_response, + headers: { "Content-Type" => "application/json" } + ) + end + let(:commit_compare_response) do + fixture("github", "commit_compare_behind.json") + end + + it { is_expected.to eq(Gem::Version.new("0.3.0")) } + + context "that is up-to-date" do + let(:commit_compare_response) do + fixture("github", "commit_compare_identical.json") + end + + # Still make an update as we wish to switch source declaration style. + # (Could decide not to do this if it causes trouble.) + it { is_expected.to eq(Gem::Version.new("0.3.0")) } + end + + context "when the new version isn't a direct update to the old one" do + let(:commit_compare_response) do + fixture("github", "commit_compare_diverged.json") + end + + # Still make an update as we wish to switch source declaration style. + # (Could decide not to do this if it causes trouble.) + it { is_expected.to eq("v0.3.0") } + end + end + end + end +end diff --git a/spec/dependabot/update_checkers/go/dep_spec.rb b/spec/dependabot/update_checkers/go/dep_spec.rb index 5539ea52bf6..c2ad73ae83a 100644 --- a/spec/dependabot/update_checkers/go/dep_spec.rb +++ b/spec/dependabot/update_checkers/go/dep_spec.rb @@ -72,102 +72,16 @@ describe "#latest_version" do subject { checker.latest_version } - it { is_expected.to eq(Gem::Version.new("0.3.0")) } - context "with a sub-dependency" do - let(:requirements) { [] } - it { is_expected.to eq(Gem::Version.new("0.3.0")) } - end - - context "with a git source" do - context "that specifies a branch" do - let(:manifest_fixture_name) { "branch.toml" } - let(:lockfile_fixture_name) { "branch.lock" } - - let(:source) do - { - type: "git", - url: "https://github.com/golang/text", - branch: "master", - ref: nil - } - end - - before do - repo_url = "https://api.github.com/repos/golang/text" - stub_request(:get, repo_url + "/compare/v0.3.0...master"). - to_return( - status: 200, - body: commit_compare_response, - headers: { "Content-Type" => "application/json" } - ) - end - - context "that is behind the latest release" do - let(:commit_compare_response) do - fixture("github", "commit_compare_behind.json") - end - - it { is_expected.to eq(Gem::Version.new("0.3.0")) } - end - - context "that is diverged from the latest release" do - let(:commit_compare_response) do - fixture("github", "commit_compare_diverged.json") - end - - it { is_expected.to eq("0605a8320aceb4207a5fb3521281e17ec2075476") } - end - end - - context "that specifies a tag" do - let(:manifest_fixture_name) { "tag_as_revision.toml" } - let(:lockfile_fixture_name) { "tag_as_revision.lock" } - - let(:source) do - { - type: "git", - url: "https://github.com/golang/text", - branch: nil, - ref: "v0.2.0" - } - end - - before do - repo_url = "https://api.github.com/repos/golang/text" - stub_request(:get, repo_url + "/compare/v0.3.0...v0.2.0"). - to_return( - status: 200, - body: commit_compare_response, - headers: { "Content-Type" => "application/json" } - ) - end - let(:commit_compare_response) do - fixture("github", "commit_compare_behind.json") - end - - it { is_expected.to eq(Gem::Version.new("0.3.0")) } - - context "that is up-to-date" do - let(:commit_compare_response) do - fixture("github", "commit_compare_identical.json") - end - - # Still make an update as we wish to switch source declaration style. - # (Could decide not to do this if it causes trouble.) - it { is_expected.to eq(Gem::Version.new("0.3.0")) } - end - - context "when the new version isn't a direct update to the old one" do - let(:commit_compare_response) do - fixture("github", "commit_compare_diverged.json") - end + it "delegates to LatestVersionFinder" do + expect(described_class::LatestVersionFinder).to receive(:new).with( + dependency: dependency, + dependency_files: dependency_files, + credentials: credentials, + ignored_versions: ignored_versions + ).and_call_original - # Still make an update as we wish to switch source declaration style. - # (Could decide not to do this if it causes trouble.) - it { is_expected.to eq("v0.3.0") } - end - end + expect(checker.latest_version).to eq(Gem::Version.new("0.3.0")) end end end From 038075a3865386de31fbe32ca84415fff8247090 Mon Sep 17 00:00:00 2001 From: Grey Baker Date: Sun, 22 Jul 2018 17:44:25 +0100 Subject: [PATCH 12/28] Dep: Add first version of latest_resolvable_version method --- .circleci/config.yml | 2 +- Dockerfile | 7 ++ lib/dependabot/file_parsers/go/dep.rb | 4 +- .../go/dep/version_resolver.rb | 104 ++++++++++++++++++ spec/dependabot/file_parsers/go/dep_spec.rb | 2 +- .../go/dep/version_resolver_spec.rb | 85 ++++++++++++++ spec/fixtures/go/gopkg_locks/no_version.lock | 4 +- 7 files changed, 202 insertions(+), 6 deletions(-) create mode 100644 lib/dependabot/update_checkers/go/dep/version_resolver.rb create mode 100644 spec/dependabot/update_checkers/go/dep/version_resolver_spec.rb diff --git a/.circleci/config.yml b/.circleci/config.yml index 35464355ae4..a041ff2f46f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,7 +2,7 @@ version: 2 jobs: build: docker: - - image: dependabot/dependabot-core:0.1.21 + - image: dependabot/dependabot-core:0.1.22 working_directory: ~/dependabot-core steps: - checkout diff --git a/Dockerfile b/Dockerfile index 90365b84b95..516421c6c88 100644 --- a/Dockerfile +++ b/Dockerfile @@ -79,6 +79,13 @@ RUN echo "deb http://ppa.launchpad.net/ondrej/php/ubuntu bionic main" >> /etc/ap && mv composer.phar /usr/local/bin/composer +### GO + +RUN curl -O https://dl.google.com/go/go1.10.3.linux-amd64.tar.gz && \ + tar xvf go1.10.3.linux-amd64.tar.gz && \ + mv go /usr/local + + ### Elixir # Install Erlang, Elixir and Hex diff --git a/lib/dependabot/file_parsers/go/dep.rb b/lib/dependabot/file_parsers/go/dep.rb index 284cdb00c33..6ca25e7ba32 100644 --- a/lib/dependabot/file_parsers/go/dep.rb +++ b/lib/dependabot/file_parsers/go/dep.rb @@ -30,7 +30,7 @@ def manifest_dependencies dependency_set = DependencySet.new REQUIREMENT_TYPES.each do |type| - parsed_file(manifest).fetch(type, {}).each do |details| + parsed_file(manifest).fetch(type, []).each do |details| dependency_set << Dependency.new( name: details.fetch("name"), version: nil, @@ -51,7 +51,7 @@ def manifest_dependencies def lockfile_dependencies dependency_set = DependencySet.new - parsed_file(lockfile).fetch("projects", {}).each do |details| + parsed_file(lockfile).fetch("projects", []).each do |details| dependency_set << Dependency.new( name: details.fetch("name"), version: version_from_lockfile(details), diff --git a/lib/dependabot/update_checkers/go/dep/version_resolver.rb b/lib/dependabot/update_checkers/go/dep/version_resolver.rb new file mode 100644 index 00000000000..6079008dddc --- /dev/null +++ b/lib/dependabot/update_checkers/go/dep/version_resolver.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true + +require "toml-rb" +require "dependabot/shared_helpers" +require "dependabot/update_checkers/go/dep" +require "dependabot/errors" + +module Dependabot + module UpdateCheckers + module Go + class Dep + class VersionResolver + def initialize(dependency:, dependency_files:, credentials:) + @dependency = dependency + @dependency_files = dependency_files + @credentials = credentials + end + + def latest_resolvable_version + @latest_resolvable_version ||= fetch_latest_resolvable_version + end + + private + + attr_reader :dependency, :dependency_files, :credentials + + def fetch_latest_resolvable_version + updated_version = + Dir.chdir(go_dir) do + write_temporary_dependency_files + + SharedHelpers.with_git_configured(credentials: credentials) do + # Shell out to dep, which handles everything for us, and does + # so without doing an install (so it's fast). + command = "dep ensure -update --no-vendor #{dependency.name}" + run_shell_command(command) + end + + new_lockfile_content = File.read("Gopkg.lock") + + get_version_from_lockfile(new_lockfile_content) + end + + FileUtils.rm_rf(go_dir) + updated_version + end + + def get_version_from_lockfile(lockfile_content) + package = TomlRB.parse(lockfile_content).fetch("projects"). + find { |p| p["name"] == dependency.name } + + if package["version"] + version_class.new(package["version"].sub(/^v?/, "")) + else + package.fetch("revision") + end + end + + def run_shell_command(command) + raw_response = nil + IO.popen(command, err: %i(child out)) do |process| + raw_response = process.read + end + + # Raise an error with the output from the shell session if dep + # returns a non-zero status + return if $CHILD_STATUS.success? + raise SharedHelpers::HelperSubprocessFailed.new( + raw_response, + command + ) + end + + def write_temporary_dependency_files + dependency_files.each do |file| + path = file.name + FileUtils.mkdir_p(Pathname.new(path).dirname) + File.write(file.name, file.content) + end + + File.write("hello.go", dummy_app_content) + end + + def go_dir + # Work in a directory called "$HOME/go/src/dependabot-tmp". + # TODO: This should pick up what the user's actual GOPATH is. + go_dir = File.join(Dir.home, "go", "src", "dependabot-tmp") + FileUtils.mkdir_p(go_dir) + go_dir + end + + def dummy_app_content + "package main\n\nimport \"fmt\"\n\nfunc main() {\n"\ + " fmt.Printf(\"hello, world\\n\")\n}" + end + + def version_class + Utils.version_class_for_package_manager(dependency.package_manager) + end + end + end + end + end +end diff --git a/spec/dependabot/file_parsers/go/dep_spec.rb b/spec/dependabot/file_parsers/go/dep_spec.rb index 16caea9da18..deed89f8b8b 100644 --- a/spec/dependabot/file_parsers/go/dep_spec.rb +++ b/spec/dependabot/file_parsers/go/dep_spec.rb @@ -75,7 +75,7 @@ it "has the right details" do expect(dependency).to be_a(Dependabot::Dependency) expect(dependency.name).to eq("golang.org/x/text") - expect(dependency.version).to eq("0.3.0") + expect(dependency.version).to eq("0.2.0") expect(dependency.requirements).to eq( [{ requirement: nil, diff --git a/spec/dependabot/update_checkers/go/dep/version_resolver_spec.rb b/spec/dependabot/update_checkers/go/dep/version_resolver_spec.rb new file mode 100644 index 00000000000..0a325ba3e6e --- /dev/null +++ b/spec/dependabot/update_checkers/go/dep/version_resolver_spec.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require "spec_helper" +require "dependabot/dependency_file" +require "dependabot/update_checkers/go/dep/version_resolver" + +RSpec.describe Dependabot::UpdateCheckers::Go::Dep::VersionResolver do + subject(:resolver) do + described_class.new( + dependency: dependency, + dependency_files: dependency_files, + credentials: credentials + ) + end + + let(:credentials) do + [{ + "type" => "git_source", + "host" => "github.com", + "username" => "x-access-token", + "password" => "token" + }] + end + let(:dependency_files) do + [ + Dependabot::DependencyFile.new( + name: "Gopkg.toml", + content: fixture("go", "gopkg_tomls", manifest_fixture_name) + ), + Dependabot::DependencyFile.new( + name: "Gopkg.lock", + content: fixture("go", "gopkg_locks", lockfile_fixture_name) + ) + ] + end + let(:manifest_fixture_name) { "no_version.toml" } + let(:lockfile_fixture_name) { "no_version.lock" } + let(:dependency) do + Dependabot::Dependency.new( + name: dependency_name, + version: dependency_version, + requirements: requirements, + package_manager: "cargo" + ) + end + let(:dependency) do + Dependabot::Dependency.new( + name: dependency_name, + version: dependency_version, + requirements: requirements, + package_manager: "dep" + ) + end + let(:requirements) do + [{ file: "Gopkg.toml", requirement: req_str, groups: [], source: source }] + end + let(:dependency_name) { "golang.org/x/text" } + let(:dependency_version) { "0.2.0" } + let(:req_str) { nil } + let(:source) { { type: "default", source: "golang.org/x/text" } } + + describe "latest_resolvable_version" do + subject(:latest_resolvable_version) { resolver.latest_resolvable_version } + + it { is_expected.to be >= Gem::Version.new("0.3.0") } + + context "with a git dependency" do + context "that specifies a branch" do + let(:manifest_fixture_name) { "branch.toml" } + let(:lockfile_fixture_name) { "branch.lock" } + + let(:source) do + { + type: "git", + url: "https://github.com/golang/text", + branch: "master", + ref: nil + } + end + + it { is_expected.to eq("0605a8320aceb4207a5fb3521281e17ec2075476") } + end + end + end +end diff --git a/spec/fixtures/go/gopkg_locks/no_version.lock b/spec/fixtures/go/gopkg_locks/no_version.lock index b47d5c63e4a..b8215c52961 100644 --- a/spec/fixtures/go/gopkg_locks/no_version.lock +++ b/spec/fixtures/go/gopkg_locks/no_version.lock @@ -17,8 +17,8 @@ "unicode/cldr", "unicode/norm" ] - revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" - version = "v0.3.0" + revision = "c4d099d611ac3ded35360abf03581e13d91c828f" + version = "v0.2.0" [solve-meta] analyzer-name = "dep" From 0477ce9992ce6576404861db204b8172edec5c29 Mon Sep 17 00:00:00 2001 From: Grey Baker Date: Sun, 22 Jul 2018 19:00:58 +0100 Subject: [PATCH 13/28] Dep: Add FilePreparer class for UpdateChecker --- .../update_checkers/go/dep/file_preparer.rb | 195 +++++++++++++++ spec/dependabot/file_parsers/go/dep_spec.rb | 4 +- .../go/dep/file_preparer_spec.rb | 232 ++++++++++++++++++ .../fixtures/go/gopkg_locks/bare_version.lock | 28 +++ .../go/gopkg_locks/tag_as_revision.lock | 2 +- .../fixtures/go/gopkg_tomls/bare_version.toml | 8 + .../go/gopkg_tomls/tag_as_revision.toml | 2 +- 7 files changed, 467 insertions(+), 4 deletions(-) create mode 100644 lib/dependabot/update_checkers/go/dep/file_preparer.rb create mode 100644 spec/dependabot/update_checkers/go/dep/file_preparer_spec.rb create mode 100644 spec/fixtures/go/gopkg_locks/bare_version.lock create mode 100644 spec/fixtures/go/gopkg_tomls/bare_version.toml diff --git a/lib/dependabot/update_checkers/go/dep/file_preparer.rb b/lib/dependabot/update_checkers/go/dep/file_preparer.rb new file mode 100644 index 00000000000..f73a63ab560 --- /dev/null +++ b/lib/dependabot/update_checkers/go/dep/file_preparer.rb @@ -0,0 +1,195 @@ +# frozen_string_literal: true + +require "toml-rb" +require "dependabot/dependency_file" +require "dependabot/file_parsers/go/dep" +require "dependabot/update_checkers/go/dep" + +module Dependabot + module UpdateCheckers + module Go + class Dep + # This class takes a set of dependency files and prepares them for use + # in UpdateCheckers::Go::Dep. + class FilePreparer + def initialize(dependency_files:, dependency:, + remove_git_source: false, + unlock_requirement: true, + replacement_git_pin: nil, + latest_allowable_version: nil) + @dependency_files = dependency_files + @dependency = dependency + @unlock_requirement = unlock_requirement + @remove_git_source = remove_git_source + @replacement_git_pin = replacement_git_pin + @latest_allowable_version = latest_allowable_version + end + + def prepared_dependency_files + files = [] + + files << manifest_for_update_check + files << lockfile if lockfile + + files + end + + private + + attr_reader :dependency_files, :dependency, :replacement_git_pin, + :latest_allowable_version + + def unlock_requirement? + @unlock_requirement + end + + def remove_git_source? + @remove_git_source + end + + def replace_git_pin? + !replacement_git_pin.nil? + end + + def manifest_for_update_check + DependencyFile.new( + name: manifest.name, + content: manifest_content_for_update_check(manifest), + directory: manifest.directory + ) + end + + def manifest_content_for_update_check(file) + content = file.content + + content = remove_git_source(content) if remove_git_source? + content = replace_git_pin(content) if replace_git_pin? + content = replace_version_constraint(content, file.name) + + content + end + + def remove_git_source(content) + parsed_manifest = TomlRB.parse(content) + + FileParsers::Go::Dep::REQUIREMENT_TYPES.each do |type| + (parsed_manifest[type] || []).each do |details| + next unless details["name"] == dependency.name + + unless details["branch"] || details["revision"] + raise "No git source to remove! #{details}" + end + details.delete("revision") + details.delete("branch") + end + end + + TomlRB.dump(parsed_manifest) + end + + # Note: We don't need to care about formatting in this method, since + # we're only using the manifest to find the latest resolvable version + def replace_version_constraint(content, filename) + parsed_manifest = TomlRB.parse(content) + + FileParsers::Go::Dep::REQUIREMENT_TYPES.each do |type| + (parsed_manifest[type] || []).each do |details| + next unless details["name"] == dependency.name + next if details["revision"] || details["branch"] + updated_req = temporary_requirement_for_resolution(filename) + + details["version"] = updated_req + end + end + + TomlRB.dump(parsed_manifest) + end + + def replace_git_pin(content) + parsed_manifest = TomlRB.parse(content) + + FileParsers::Go::Dep::REQUIREMENT_TYPES.each do |type| + (parsed_manifest[type] || []).each do |details| + next unless details["name"] == dependency.name + + if details["branch"] || details["version"] + raise "Invalid details! #{details}" + end + details["revision"] = replacement_git_pin + end + end + + TomlRB.dump(parsed_manifest) + end + + def temporary_requirement_for_resolution(filename) + original_req = dependency.requirements. + find { |r| r.fetch(:file) == filename }&. + fetch(:requirement) + + lower_bound_req = + if original_req && !unlock_requirement? + original_req + else + ">= #{lower_bound_version}" + end + + unless version_class.correct?(latest_allowable_version) && + version_class.new(latest_allowable_version) >= + version_class.new(lower_bound_version) + return lower_bound_req + end + + lower_bound_req + ", <= #{latest_allowable_version}" + end + + def lower_bound_version + @lower_bound_version ||= + if version_from_lockfile + version_from_lockfile + else + version_from_requirement = + dependency.requirements.map { |r| r.fetch(:requirement) }. + compact. + flat_map { |req_str| requirement_class.new(req_str) }. + flat_map(&:requirements). + reject { |req_array| req_array.first.start_with?("<") }. + map(&:last). + max&.to_s + + version_from_requirement || 0 + end + end + + def version_from_lockfile + return unless lockfile + + TomlRB.parse(lockfile.content). + fetch("projects", []). + find { |p| p["name"] == dependency.name }&. + fetch("version", nil)&. + sub(/^v?/, "") + end + + def version_class + Utils.version_class_for_package_manager(dependency.package_manager) + end + + def requirement_class + Utils.requirement_class_for_package_manager( + dependency.package_manager + ) + end + + def manifest + @manifest ||= dependency_files.find { |f| f.name == "Gopkg.toml" } + end + + def lockfile + @lockfile ||= dependency_files.find { |f| f.name == "Gopkg.lock" } + end + end + end + end + end +end diff --git a/spec/dependabot/file_parsers/go/dep_spec.rb b/spec/dependabot/file_parsers/go/dep_spec.rb index deed89f8b8b..5303a75d070 100644 --- a/spec/dependabot/file_parsers/go/dep_spec.rb +++ b/spec/dependabot/file_parsers/go/dep_spec.rb @@ -123,7 +123,7 @@ it "has the right details" do expect(dependency).to be_a(Dependabot::Dependency) expect(dependency.name).to eq("golang.org/x/text") - expect(dependency.version).to eq("v0.3.0") + expect(dependency.version).to eq("v0.2.0") expect(dependency.requirements).to eq( [{ requirement: nil, @@ -133,7 +133,7 @@ type: "git", url: "https://github.com/golang/text", branch: nil, - ref: "v0.3.0" + ref: "v0.2.0" } }] ) diff --git a/spec/dependabot/update_checkers/go/dep/file_preparer_spec.rb b/spec/dependabot/update_checkers/go/dep/file_preparer_spec.rb new file mode 100644 index 00000000000..90b28beed3f --- /dev/null +++ b/spec/dependabot/update_checkers/go/dep/file_preparer_spec.rb @@ -0,0 +1,232 @@ +# frozen_string_literal: true + +require "spec_helper" +require "dependabot/dependency" +require "dependabot/update_checkers/go/dep/file_preparer" + +RSpec.describe Dependabot::UpdateCheckers::Go::Dep::FilePreparer do + let(:preparer) do + described_class.new( + dependency_files: dependency_files, + dependency: dependency, + unlock_requirement: unlock_requirement, + remove_git_source: remove_git_source, + replacement_git_pin: replacement_git_pin, + latest_allowable_version: latest_allowable_version + ) + end + + let(:dependency_files) { [manifest, lockfile] } + let(:unlock_requirement) { true } + let(:remove_git_source) { false } + let(:replacement_git_pin) { nil } + let(:latest_allowable_version) { nil } + + let(:manifest) do + Dependabot::DependencyFile.new( + name: "Gopkg.toml", + content: fixture("go", "gopkg_tomls", manifest_fixture_name) + ) + end + let(:lockfile) do + Dependabot::DependencyFile.new( + name: "Gopkg.lock", + content: fixture("go", "gopkg_locks", lockfile_fixture_name) + ) + end + let(:manifest_fixture_name) { "bare_version.toml" } + let(:lockfile_fixture_name) { "bare_version.lock" } + let(:dependency) do + Dependabot::Dependency.new( + name: dependency_name, + version: dependency_version, + requirements: requirements, + package_manager: "dep" + ) + end + let(:requirements) do + [{ + file: "Gopkg.toml", + requirement: string_req, + groups: [], + source: source + }] + end + let(:dependency_name) { "golang.org/x/text" } + let(:dependency_version) { "0.2.0" } + let(:string_req) { "0.2.0" } + let(:source) { { type: "default", source: "golang.org/x/text" } } + + describe "#prepared_dependency_files" do + subject(:prepared_dependency_files) { preparer.prepared_dependency_files } + + its(:length) { is_expected.to eq(2) } + + describe "the updated Gopkg.toml" do + subject(:prepared_manifest_file) do + prepared_dependency_files.find { |f| f.name == "Gopkg.toml" } + end + + context "with unlock_requirement set to false" do + let(:unlock_requirement) { false } + + it "doesn't update the requirement" do + expect(prepared_manifest_file.content).to include('version = "0.2.0"') + end + end + + context "with unlock_requirement set to true" do + let(:unlock_requirement) { true } + + it "updates the requirement" do + expect(prepared_manifest_file.content). + to include('version = ">= 0.2.0"') + end + + context "without a lockfile" do + let(:dependency_files) { [manifest] } + let(:dependency_version) { nil } + let(:string_req) { "0.2.0" } + + it "updates the requirement" do + expect(prepared_manifest_file.content). + to include('version = ">= 0.2.0"') + end + end + + context "with a blank requirement" do + let(:manifest_fixture_name) { "no_version.toml" } + let(:lockfile_fixture_name) { "no_version.lock" } + let(:string_req) { nil } + + it "updates the requirement" do + expect(prepared_manifest_file.content). + to include('version = ">= 0.2.0"') + end + + context "and a latest_allowable_version" do + let(:latest_allowable_version) { Gem::Version.new("1.6.0") } + + it "updates the requirement" do + expect(prepared_manifest_file.content). + to include('version = ">= 0.2.0, <= 1.6.0"') + end + + context "that is lower than the current lower bound" do + let(:latest_allowable_version) { Gem::Version.new("0.1.0") } + + it "updates the requirement" do + expect(prepared_manifest_file.content). + to include('version = ">= 0.2.0"') + end + end + end + + context "without a lockfile" do + let(:dependency_files) { [manifest] } + let(:dependency_version) { nil } + let(:string_req) { nil } + + it "updates the requirement" do + expect(prepared_manifest_file.content). + to include('version = ">= 0"') + end + end + end + + context "with a git requirement" do + context "with a branch" do + let(:manifest_fixture_name) { "branch.toml" } + let(:lockfile_fixture_name) { "branch.lock" } + let(:dependency_version) do + "7dd2c8130f5e924233f5543598300651c386d431" + end + let(:string_req) { nil } + let(:source) do + { + type: "git", + url: "https://github.com/golang/text", + branch: "master", + ref: nil + } + end + + it "doesn't update the manifest" do + expect(prepared_manifest_file.content). + to_not include("version =") + end + + context "that we want to remove" do + let(:remove_git_source) { true } + + it "removes the git source" do + expect(prepared_manifest_file.content). + to include('version = ">= 0"') + expect(prepared_manifest_file.content). + to_not include("branch = ") + end + end + end + + context "with a tag" do + let(:manifest_fixture_name) { "tag_as_revision.toml" } + let(:lockfile_fixture_name) { "tag_as_revision.lock" } + let(:dependency_version) { "v0.2.0" } + let(:string_req) { nil } + let(:source) do + { + type: "git", + url: "https://github.com/golang/text", + branch: nil, + ref: "v0.2.0" + } + end + + context "without a replacement tag" do + let(:replacement_git_pin) { nil } + + it "doesn't update the tag" do + expect(prepared_manifest_file.content). + to include('revision = "v0.2.0"') + expect(prepared_manifest_file.content). + to_not include("version =") + end + + context "when we want to remove the tag" do + let(:remove_git_source) { true } + + it "removes the git source" do + expect(prepared_manifest_file.content). + to include('version = ">= 0"') + expect(prepared_manifest_file.content). + to_not include("revision = ") + end + end + end + + context "with a replacement tag" do + let(:replacement_git_pin) { "v1.0.0" } + + it "updates the requirement" do + expect(prepared_manifest_file.content). + to include('revision = "v1.0.0"') + expect(prepared_manifest_file.content). + to_not include("version =") + end + end + end + end + end + end + + describe "the updated lockfile" do + subject { prepared_dependency_files.find { |f| f.name == "Gopkg.lock" } } + it { is_expected.to eq(lockfile) } + end + + context "without a lockfile" do + let(:dependency_files) { [manifest] } + its(:length) { is_expected.to eq(1) } + end + end +end diff --git a/spec/fixtures/go/gopkg_locks/bare_version.lock b/spec/fixtures/go/gopkg_locks/bare_version.lock new file mode 100644 index 00000000000..f94dce0d34a --- /dev/null +++ b/spec/fixtures/go/gopkg_locks/bare_version.lock @@ -0,0 +1,28 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + name = "golang.org/x/text" + packages = [ + ".", + "collate", + "collate/build", + "internal/colltab", + "internal/gen", + "internal/tag", + "internal/triegen", + "internal/ucd", + "language", + "transform", + "unicode/cldr", + "unicode/norm" + ] + revision = "c4d099d611ac3ded35360abf03581e13d91c828f" + version = "v0.2.0" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "72ff7ba321731c2b453c810f427e9d5a487f184083398167f21b3d7b565ab41c" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/spec/fixtures/go/gopkg_locks/tag_as_revision.lock b/spec/fixtures/go/gopkg_locks/tag_as_revision.lock index 9f6dccfb523..f7ba7ae3ae3 100644 --- a/spec/fixtures/go/gopkg_locks/tag_as_revision.lock +++ b/spec/fixtures/go/gopkg_locks/tag_as_revision.lock @@ -17,7 +17,7 @@ "unicode/cldr", "unicode/norm" ] - revision = "v0.3.0" + revision = "v0.2.0" [solve-meta] analyzer-name = "dep" diff --git a/spec/fixtures/go/gopkg_tomls/bare_version.toml b/spec/fixtures/go/gopkg_tomls/bare_version.toml new file mode 100644 index 00000000000..4c9ac65a273 --- /dev/null +++ b/spec/fixtures/go/gopkg_tomls/bare_version.toml @@ -0,0 +1,8 @@ +required = [ + "golang.org/x/text", + ] + +# The collation tables must never change. +[[constraint]] + name = "golang.org/x/text" + version = "0.2.0" diff --git a/spec/fixtures/go/gopkg_tomls/tag_as_revision.toml b/spec/fixtures/go/gopkg_tomls/tag_as_revision.toml index c1a178d00c6..c785976ea32 100644 --- a/spec/fixtures/go/gopkg_tomls/tag_as_revision.toml +++ b/spec/fixtures/go/gopkg_tomls/tag_as_revision.toml @@ -5,4 +5,4 @@ required = [ # The collation tables must never change. [[constraint]] name = "golang.org/x/text" - revision = "v0.3.0" + revision = "v0.2.0" From b25520a75dc7d865b900cb32c784f987020533ea Mon Sep 17 00:00:00 2001 From: Grey Baker Date: Sun, 22 Jul 2018 19:38:03 +0100 Subject: [PATCH 14/28] Dep: Include dep in the Dockerfile --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 516421c6c88..992fba4f34c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -83,7 +83,8 @@ RUN echo "deb http://ppa.launchpad.net/ondrej/php/ubuntu bionic main" >> /etc/ap RUN curl -O https://dl.google.com/go/go1.10.3.linux-amd64.tar.gz && \ tar xvf go1.10.3.linux-amd64.tar.gz && \ - mv go /usr/local + wget https://github.com/golang/dep/releases/download/v0.4.1/dep-linux-amd64 && \ + mv dep-linux-amd64 go/bin/dep && mv go /usr/local ### Elixir From 708a89ff618624d4bbadd5df87a39489aace2c4e Mon Sep 17 00:00:00 2001 From: Grey Baker Date: Sun, 22 Jul 2018 19:44:48 +0100 Subject: [PATCH 15/28] Dep: Hook up UpdateChecker for dependencies that specify a version --- lib/dependabot/update_checkers/go/dep.rb | 75 +++++++++++-------- .../go/dep/version_resolver.rb | 27 ++++++- .../dependabot/update_checkers/go/dep_spec.rb | 67 +++++++++++++++++ 3 files changed, 136 insertions(+), 33 deletions(-) diff --git a/lib/dependabot/update_checkers/go/dep.rb b/lib/dependabot/update_checkers/go/dep.rb index 25f1062cd0d..46e67b1a40f 100644 --- a/lib/dependabot/update_checkers/go/dep.rb +++ b/lib/dependabot/update_checkers/go/dep.rb @@ -6,7 +6,9 @@ module Dependabot module UpdateCheckers module Go class Dep < Dependabot::UpdateCheckers::Base + require_relative "dep/file_preparer" require_relative "dep/latest_version_finder" + require_relative "dep/version_resolver" def latest_version @latest_version ||= @@ -19,20 +21,28 @@ def latest_version end def latest_resolvable_version - # Resolving the dependency files to get the latest version of - # this dependency that doesn't cause conflicts is hard, and needs to - # be done through a language helper that piggy-backs off of the - # package manager's own resolution logic (see PHP, for example). + @latest_resolvable_version ||= + if git_dependency? + latest_resolvable_version_for_git_dependency + else + fetch_latest_resolvable_version(unlock_requirement: true) + end end def latest_resolvable_version_with_no_unlock - # Will need the same resolution logic as above, just without the - # file unlocking. + @latest_resolvable_version_with_no_unlock ||= + if git_dependency? + latest_resolvable_commit_with_unchanged_git_source + else + fetch_latest_resolvable_version(unlock_requirement: false) + end end def updated_requirements # If the dependency file needs to be updated we store the updated # requirements on the dependency. + # + # TODO! dependency.requirements end @@ -47,37 +57,40 @@ def updated_dependencies_after_full_unlock raise NotImplementedError end - def dependencies_to_import - # There's no way to tell whether dependencies that appear in the - # lockfile are there because they're imported themselves or because - # they're sub-dependencies of something else. v0.5.0 will fix that - # problem, but for now we just have to import everything. - # - # NOTE: This means the `inputs-digest` we generate will be wrong. - # That's a pity, but we'd have to iterate through too many - # possibilities to get it right. Again, this is fixed in v0.5.0. - parsed_file(lockfile).fetch("required").map do |detail| - detail["name"] - end + def latest_resolvable_version_for_git_dependency + # TODO + end + + def latest_resolvable_commit_with_unchanged_git_source + # TODO end - def parsed_file(file) - @parsed_file ||= {} - @parsed_file[file.name] ||= TomlRB.parse(file.content) - rescue TomlRB::ParseError - raise Dependabot::DependencyFileNotParseable, file.path + def fetch_latest_resolvable_version(unlock_requirement:) + prepared_files = FilePreparer.new( + dependency_files: dependency_files, + dependency: dependency, + unlock_requirement: unlock_requirement, + remove_git_source: git_dependency?, + latest_allowable_version: latest_version + ).prepared_dependency_files + + VersionResolver.new( + dependency: dependency, + dependency_files: prepared_files, + credentials: credentials + ).latest_resolvable_version end - def manifest - @manifest ||= dependency_files.find { |f| f.name == "Gopkg.toml" } - raise "No Gopkg.lock!" unless @manifest - @manifest + def git_dependency? + git_commit_checker.git_dependency? end - def lockfile - @lockfile = dependency_files.find { |f| f.name == "Gopkg.lock" } - raise "No Gopkg.lock!" unless @lockfile - @lockfile + def git_commit_checker + @git_commit_checker ||= + GitCommitChecker.new( + dependency: dependency, + credentials: credentials + ) end end end diff --git a/lib/dependabot/update_checkers/go/dep/version_resolver.rb b/lib/dependabot/update_checkers/go/dep/version_resolver.rb index 6079008dddc..7255830dc34 100644 --- a/lib/dependabot/update_checkers/go/dep/version_resolver.rb +++ b/lib/dependabot/update_checkers/go/dep/version_resolver.rb @@ -90,8 +90,31 @@ def go_dir end def dummy_app_content - "package main\n\nimport \"fmt\"\n\nfunc main() {\n"\ - " fmt.Printf(\"hello, world\\n\")\n}" + base = "package main\n\n"\ + "import \"fmt\"\n\n" + + dependencies_to_import.each { |nm| base += "import \"#{nm}\"\n\n" } + + base + "func main() {\n fmt.Printf(\"hello, world\\n\")\n}" + end + + def dependencies_to_import + # There's no way to tell whether dependencies that appear in the + # lockfile are there because they're imported themselves or because + # they're sub-dependencies of something else. v0.5.0 will fix that + # problem, but for now we just have to import everything. + # + # NOTE: This means the `inputs-digest` we generate will be wrong. + # That's a pity, but we'd have to iterate through too many + # possibilities to get it right. Again, this is fixed in v0.5.0. + return [] unless lockfile + TomlRB.parse(lockfile.content).fetch("projects").map do |detail| + detail["name"] + end + end + + def lockfile + @lockfile = dependency_files.find { |f| f.name == "Gopkg.lock" } end def version_class diff --git a/spec/dependabot/update_checkers/go/dep_spec.rb b/spec/dependabot/update_checkers/go/dep_spec.rb index c2ad73ae83a..95e768f03e7 100644 --- a/spec/dependabot/update_checkers/go/dep_spec.rb +++ b/spec/dependabot/update_checkers/go/dep_spec.rb @@ -84,4 +84,71 @@ expect(checker.latest_version).to eq(Gem::Version.new("0.3.0")) end end + + describe "#latest_resolvable_version" do + subject { checker.latest_resolvable_version } + + it "delegates to VersionResolver" do + prepped_files = described_class::FilePreparer.new( + dependency_files: dependency_files, + dependency: dependency, + unlock_requirement: true, + remove_git_source: false, + latest_allowable_version: Gem::Version.new("0.3.0") + ).prepared_dependency_files + + expect(described_class::VersionResolver).to receive(:new).with( + dependency: dependency, + dependency_files: prepped_files, + credentials: credentials + ).and_call_original + + expect(checker.latest_resolvable_version).to eq(Gem::Version.new("0.3.0")) + end + + context "with a manifest file that needs unlocking" do + let(:manifest_fixture_name) { "bare_version.toml" } + let(:lockfile_fixture_name) { "bare_version.lock" } + let(:req_str) { "0.2.0" } + + it "unlocks the manifest and gets the correct version" do + expect(checker.latest_resolvable_version). + to eq(Gem::Version.new("0.3.0")) + end + end + end + + describe "#latest_resolvable_version_with_no_unlock" do + subject { checker.latest_resolvable_version_with_no_unlock } + + it "delegates to VersionResolver" do + prepped_files = described_class::FilePreparer.new( + dependency_files: dependency_files, + dependency: dependency, + unlock_requirement: true, + remove_git_source: false, + latest_allowable_version: Gem::Version.new("0.3.0") + ).prepared_dependency_files + + expect(described_class::VersionResolver).to receive(:new).with( + dependency: dependency, + dependency_files: prepped_files, + credentials: credentials + ).and_call_original + + expect(checker.latest_resolvable_version_with_no_unlock). + to eq(Gem::Version.new("0.3.0")) + end + + context "with a manifest file that needs unlocking" do + let(:manifest_fixture_name) { "bare_version.toml" } + let(:lockfile_fixture_name) { "bare_version.lock" } + let(:req_str) { "0.2.0" } + + it "doesn't unlock the manifest" do + expect(checker.latest_resolvable_version_with_no_unlock). + to eq(Gem::Version.new("0.2.0")) + end + end + end end From 832bd97fcad80f6d5f61a6d0e72c4aba0b585bd1 Mon Sep 17 00:00:00 2001 From: Grey Baker Date: Tue, 24 Jul 2018 08:19:48 +0100 Subject: [PATCH 16/28] Get Dockerfile right --- Dockerfile | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 992fba4f34c..c32590beb08 100644 --- a/Dockerfile +++ b/Dockerfile @@ -81,10 +81,13 @@ RUN echo "deb http://ppa.launchpad.net/ondrej/php/ubuntu bionic main" >> /etc/ap ### GO -RUN curl -O https://dl.google.com/go/go1.10.3.linux-amd64.tar.gz && \ - tar xvf go1.10.3.linux-amd64.tar.gz && \ - wget https://github.com/golang/dep/releases/download/v0.4.1/dep-linux-amd64 && \ - mv dep-linux-amd64 go/bin/dep && mv go /usr/local +RUN curl -O https://dl.google.com/go/go1.10.3.linux-amd64.tar.gz \ + && tar xvf go1.10.3.linux-amd64.tar.gz \ + && wget https://github.com/golang/dep/releases/download/v0.4.1/dep-linux-amd64 \ + && mv dep-linux-amd64 go/bin/dep \ + && chmod +x go/bin/dep \ + && mv go /root +ENV PATH=/root/go/bin:$PATH ### Elixir From 448ad75763d066f557bdf94e3385306df5a7f410 Mon Sep 17 00:00:00 2001 From: Grey Baker Date: Tue, 24 Jul 2018 10:21:18 +0100 Subject: [PATCH 17/28] Dep: Handle git dependencies properly in UpdateCheckers::Go::Dep#latest_resolvable_version --- lib/dependabot/update_checkers/go/dep.rb | 95 +++++++++++-- .../go/dep/latest_version_finder.rb | 14 -- lib/dependabot/update_checkers/rust/cargo.rb | 2 +- .../go/dep/version_resolver_spec.rb | 16 +++ .../dependabot/update_checkers/go/dep_spec.rb | 126 +++++++++++++++++- 5 files changed, 224 insertions(+), 29 deletions(-) diff --git a/lib/dependabot/update_checkers/go/dep.rb b/lib/dependabot/update_checkers/go/dep.rb index 46e67b1a40f..6e8d715afa9 100644 --- a/lib/dependabot/update_checkers/go/dep.rb +++ b/lib/dependabot/update_checkers/go/dep.rb @@ -25,7 +25,7 @@ def latest_resolvable_version if git_dependency? latest_resolvable_version_for_git_dependency else - fetch_latest_resolvable_version(unlock_requirement: true) + latest_resolvable_released_version(unlock_requirement: true) end end @@ -34,7 +34,7 @@ def latest_resolvable_version_with_no_unlock if git_dependency? latest_resolvable_commit_with_unchanged_git_source else - fetch_latest_resolvable_version(unlock_requirement: false) + latest_resolvable_released_version(unlock_requirement: false) end end @@ -58,20 +58,85 @@ def updated_dependencies_after_full_unlock end def latest_resolvable_version_for_git_dependency - # TODO + latest_release = + latest_resolvable_released_version(unlock_requirement: true) + + # If there's a resolvable release that includes the current pinned + # ref or that the current branch is behind, we switch to that release. + return latest_release if git_branch_or_ref_in_release?(latest_release) + + # Otherwise, if the gem isn't pinned, the latest version is just the + # latest commit for the specified branch. + unless git_commit_checker.pinned? + return latest_resolvable_commit_with_unchanged_git_source + end + + # If the dependency is pinned to a tag that looks like a version then + # we want to update that tag. The latest version will be the + # tag name (NOT the tag SHA, unlike in other package managers). + if git_commit_checker.pinned_ref_looks_like_version? && + latest_git_tag_is_resolvable? + new_tag = git_commit_checker.local_tag_for_latest_version + return new_tag.fetch(:tag) + end + + # If the dependency is pinned to a tag that doesn't look like a + # version then there's nothing we can do. + nil end def latest_resolvable_commit_with_unchanged_git_source - # TODO + @latest_resolvable_commit_with_unchanged_git_source ||= + begin + prepared_files = FilePreparer.new( + dependency_files: dependency_files, + dependency: dependency, + unlock_requirement: false, + remove_git_source: false, + latest_allowable_version: latest_version + ).prepared_dependency_files + + VersionResolver.new( + dependency: dependency, + dependency_files: prepared_files, + credentials: credentials + ).latest_resolvable_version + end + end + + def latest_resolvable_released_version(unlock_requirement:) + @latest_resolvable_released_version ||= {} + @latest_resolvable_released_version[unlock_requirement] ||= + begin + prepared_files = FilePreparer.new( + dependency_files: dependency_files, + dependency: dependency, + unlock_requirement: unlock_requirement, + remove_git_source: git_dependency?, + latest_allowable_version: latest_version + ).prepared_dependency_files + + VersionResolver.new( + dependency: dependency, + dependency_files: prepared_files, + credentials: credentials + ).latest_resolvable_version + end end - def fetch_latest_resolvable_version(unlock_requirement:) + def latest_git_tag_is_resolvable? + return @git_tag_resolvable if @latest_git_tag_is_resolvable_checked + @latest_git_tag_is_resolvable_checked = true + + return false if git_commit_checker.local_tag_for_latest_version.nil? + replacement_tag = git_commit_checker.local_tag_for_latest_version + prepared_files = FilePreparer.new( - dependency_files: dependency_files, dependency: dependency, - unlock_requirement: unlock_requirement, - remove_git_source: git_dependency?, - latest_allowable_version: latest_version + dependency_files: dependency_files, + unlock_requirement: false, + remove_git_source: false, + replacement_git_pin: replacement_tag.fetch(:tag) ).prepared_dependency_files VersionResolver.new( @@ -79,17 +144,27 @@ def fetch_latest_resolvable_version(unlock_requirement:) dependency_files: prepared_files, credentials: credentials ).latest_resolvable_version + + @git_tag_resolvable = true + rescue Dependabot::DependencyFileNotResolvable + @git_tag_resolvable = false end def git_dependency? git_commit_checker.git_dependency? end + def git_branch_or_ref_in_release?(release) + return false unless release + git_commit_checker.branch_or_ref_in_release?(release) + end + def git_commit_checker @git_commit_checker ||= GitCommitChecker.new( dependency: dependency, - credentials: credentials + credentials: credentials, + ignored_versions: ignored_versions ) end end diff --git a/lib/dependabot/update_checkers/go/dep/latest_version_finder.rb b/lib/dependabot/update_checkers/go/dep/latest_version_finder.rb index e9f9e52fd47..89df3f80c9d 100644 --- a/lib/dependabot/update_checkers/go/dep/latest_version_finder.rb +++ b/lib/dependabot/update_checkers/go/dep/latest_version_finder.rb @@ -115,20 +115,6 @@ def branch_or_ref_in_release?(release) git_commit_checker.branch_or_ref_in_release?(release) end - def dependencies_to_import - # There's no way to tell whether dependencies that appear in the - # lockfile are there because they're imported themselves or because - # they're sub-dependencies of something else. v0.5.0 will fix that - # problem, but for now we just have to import everything. - # - # NOTE: This means the `inputs-digest` we generate will be wrong. - # That's a pity, but we'd have to iterate through too many - # possibilities to get it right. Again, this is fixed in v0.5.0. - parsed_file(lockfile).fetch("required").map do |detail| - detail["name"] - end - end - def git_dependency? git_commit_checker.git_dependency? end diff --git a/lib/dependabot/update_checkers/rust/cargo.rb b/lib/dependabot/update_checkers/rust/cargo.rb index ca106367d09..5a42af9a218 100644 --- a/lib/dependabot/update_checkers/rust/cargo.rb +++ b/lib/dependabot/update_checkers/rust/cargo.rb @@ -143,7 +143,7 @@ def latest_git_tag_is_resolvable? def latest_resolvable_commit_with_unchanged_git_source fetch_latest_resolvable_version(unlock_requirement: false) rescue SharedHelpers::HelperSubprocessFailed => error - # Resolution may fail, as Elixir updates straight to the tip of the + # Resolution may fail, as Cargo updates straight to the tip of the # branch. Just return `nil` if it does (so no update). return if error.message.include?("versions conflict") raise error diff --git a/spec/dependabot/update_checkers/go/dep/version_resolver_spec.rb b/spec/dependabot/update_checkers/go/dep/version_resolver_spec.rb index 0a325ba3e6e..cac1ed6f310 100644 --- a/spec/dependabot/update_checkers/go/dep/version_resolver_spec.rb +++ b/spec/dependabot/update_checkers/go/dep/version_resolver_spec.rb @@ -80,6 +80,22 @@ it { is_expected.to eq("0605a8320aceb4207a5fb3521281e17ec2075476") } end + + context "that specifies a tag as a revision" do + let(:manifest_fixture_name) { "tag_as_revision.toml" } + let(:lockfile_fixture_name) { "tag_as_revision.lock" } + + let(:source) do + { + type: "git", + url: "https://github.com/golang/text", + branch: nil, + ref: "v0.2.0" + } + end + + it { is_expected.to eq("v0.2.0") } + end end end end diff --git a/spec/dependabot/update_checkers/go/dep_spec.rb b/spec/dependabot/update_checkers/go/dep_spec.rb index 95e768f03e7..a54280adaab 100644 --- a/spec/dependabot/update_checkers/go/dep_spec.rb +++ b/spec/dependabot/update_checkers/go/dep_spec.rb @@ -86,7 +86,7 @@ end describe "#latest_resolvable_version" do - subject { checker.latest_resolvable_version } + subject(:latest_resolvable_version) { checker.latest_resolvable_version } it "delegates to VersionResolver" do prepped_files = described_class::FilePreparer.new( @@ -103,7 +103,7 @@ credentials: credentials ).and_call_original - expect(checker.latest_resolvable_version).to eq(Gem::Version.new("0.3.0")) + expect(latest_resolvable_version).to eq(Gem::Version.new("0.3.0")) end context "with a manifest file that needs unlocking" do @@ -112,8 +112,74 @@ let(:req_str) { "0.2.0" } it "unlocks the manifest and gets the correct version" do - expect(checker.latest_resolvable_version). - to eq(Gem::Version.new("0.3.0")) + expect(latest_resolvable_version).to eq(Gem::Version.new("0.3.0")) + end + end + + context "with a git dependency" do + let(:source) do + { + type: "git", + url: "https://github.com/golang/text", + branch: branch, + ref: ref + } + end + + before do + repo_url = "https://api.github.com/repos/golang/text" + stub_request(:get, repo_url + "/compare/v0.3.0...#{branch || ref}"). + to_return( + status: 200, + body: commit_compare_response, + headers: { "Content-Type" => "application/json" } + ) + end + let(:commit_compare_response) do + fixture("github", "commit_compare_behind.json") + end + + context "that specifies a branch" do + let(:manifest_fixture_name) { "branch.toml" } + let(:lockfile_fixture_name) { "branch.lock" } + let(:req_str) { nil } + let(:dependency_version) { "7dd2c8130f5e924233f5543598300651c386d431" } + let(:branch) { "master" } + let(:ref) { nil } + + context "that has diverged" do + let(:commit_compare_response) do + fixture("github", "commit_compare_diverged.json") + end + + it "updates the commit" do + expect(latest_resolvable_version). + to eq("0605a8320aceb4207a5fb3521281e17ec2075476") + end + end + + context "that is behind" do + let(:commit_compare_response) do + fixture("github", "commit_compare_behind.json") + end + + it "updates to a released version" do + expect(latest_resolvable_version).to eq(Gem::Version.new("0.3.0")) + end + end + end + + context "that specifies a tag" do + let(:manifest_fixture_name) { "tag_as_revision.toml" } + let(:lockfile_fixture_name) { "tag_as_revision.lock" } + let(:req_str) { nil } + let(:dependency_version) { "v0.2.0" } + let(:branch) { nil } + let(:ref) { "v0.2.0" } + + it "updates to a released version" do + expect(latest_resolvable_version).to eq(Gem::Version.new("0.3.0")) + end end end end @@ -150,5 +216,57 @@ to eq(Gem::Version.new("0.2.0")) end end + + context "with a git dependency" do + let(:source) do + { + type: "git", + url: "https://github.com/golang/text", + branch: branch, + ref: ref + } + end + + before do + repo_url = "https://api.github.com/repos/golang/text" + stub_request(:get, repo_url + "/compare/v0.3.0...#{branch || ref}"). + to_return( + status: 200, + body: commit_compare_response, + headers: { "Content-Type" => "application/json" } + ) + end + let(:commit_compare_response) do + fixture("github", "commit_compare_behind.json") + end + + context "that specifies a branch" do + let(:manifest_fixture_name) { "branch.toml" } + let(:lockfile_fixture_name) { "branch.lock" } + let(:req_str) { nil } + let(:dependency_version) { "7dd2c8130f5e924233f5543598300651c386d431" } + let(:branch) { "master" } + let(:ref) { nil } + + it "updates the commit" do + expect(checker.latest_resolvable_version_with_no_unlock). + to eq("0605a8320aceb4207a5fb3521281e17ec2075476") + end + end + + context "that specifies a tag" do + let(:manifest_fixture_name) { "tag_as_revision.toml" } + let(:lockfile_fixture_name) { "tag_as_revision.lock" } + let(:req_str) { nil } + let(:dependency_version) { "v0.2.0" } + let(:branch) { nil } + let(:ref) { "v0.2.0" } + + it "doesn't unpin the commit" do + expect(checker.latest_resolvable_version_with_no_unlock). + to eq("v0.2.0") + end + end + end end end From 976ff173ddc771995e7815fdde49925e09564a89 Mon Sep 17 00:00:00 2001 From: Grey Baker Date: Tue, 24 Jul 2018 12:35:40 +0100 Subject: [PATCH 18/28] Dep: Add utility class for Go versions --- lib/dependabot/utils.rb | 9 +- lib/dependabot/utils/go/requirement.rb | 125 ++++++++++++++++ lib/dependabot/utils/go/version.rb | 23 +++ spec/dependabot/utils/go/requirement_spec.rb | 146 +++++++++++++++++++ spec/dependabot/utils/go/version_spec.rb | 48 ++++++ 5 files changed, 348 insertions(+), 3 deletions(-) create mode 100644 lib/dependabot/utils/go/requirement.rb create mode 100644 lib/dependabot/utils/go/version.rb create mode 100644 spec/dependabot/utils/go/requirement_spec.rb create mode 100644 spec/dependabot/utils/go/version_spec.rb diff --git a/lib/dependabot/utils.rb b/lib/dependabot/utils.rb index b737901aba5..3b084408925 100644 --- a/lib/dependabot/utils.rb +++ b/lib/dependabot/utils.rb @@ -7,6 +7,7 @@ require "dependabot/utils/php/version" require "dependabot/utils/python/version" require "dependabot/utils/rust/version" +require "dependabot/utils/go/version" require "dependabot/utils/dotnet/requirement" require "dependabot/utils/elixir/requirement" @@ -16,13 +17,14 @@ require "dependabot/utils/python/requirement" require "dependabot/utils/ruby/requirement" require "dependabot/utils/rust/requirement" +require "dependabot/utils/go/requirement" # rubocop:disable Metrics/CyclomaticComplexity module Dependabot module Utils def self.version_class_for_package_manager(package_manager) case package_manager - when "bundler", "submodules", "docker", "dep" then Gem::Version + when "bundler", "submodules", "docker" then Gem::Version when "nuget" then Utils::Dotnet::Version when "maven" then Utils::Java::Version when "gradle" then Utils::Java::Version @@ -31,14 +33,14 @@ def self.version_class_for_package_manager(package_manager) when "composer" then Utils::Php::Version when "hex" then Utils::Elixir::Version when "cargo" then Utils::Rust::Version + when "dep" then Utils::Go::Version else raise "Unsupported package_manager #{package_manager}" end end def self.requirement_class_for_package_manager(package_manager) case package_manager - when "bundler", "submodules", "docker", "dep" - Utils::Ruby::Requirement + when "bundler", "submodules", "docker" then Utils::Ruby::Requirement when "nuget" then Utils::Dotnet::Requirement when "maven" then Utils::Java::Requirement when "gradle" then Utils::Java::Requirement @@ -47,6 +49,7 @@ def self.requirement_class_for_package_manager(package_manager) when "composer" then Utils::Php::Requirement when "hex" then Utils::Elixir::Requirement when "cargo" then Utils::Rust::Requirement + when "dep" then Utils::Go::Requirement else raise "Unsupported package_manager #{package_manager}" end end diff --git a/lib/dependabot/utils/go/requirement.rb b/lib/dependabot/utils/go/requirement.rb new file mode 100644 index 00000000000..81d28e172a8 --- /dev/null +++ b/lib/dependabot/utils/go/requirement.rb @@ -0,0 +1,125 @@ +# frozen_string_literal: true + +################################################################################ +# For more details on Go version constraints, see: # +# - https://github.com/Masterminds/semver # +# - https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md # +################################################################################ + +require "dependabot/utils/go/version" + +module Dependabot + module Utils + module Go + class Requirement < Gem::Requirement + WILDCARD_REGEX = /(?:\.|^)[xX*]/ + + # Use Utils::Go::Version rather than Gem::Version to ensure that + # pre-release versions aren't transformed. + def self.parse(obj) + if obj.is_a?(Gem::Version) + return ["=", Utils::Go::Version.new(obj.to_s)] + end + + unless (matches = PATTERN.match(obj.to_s)) + msg = "Illformed requirement [#{obj.inspect}]" + raise BadRequirementError, msg + end + + return DefaultRequirement if matches[1] == ">=" && matches[2] == "0" + [matches[1] || "=", Utils::Go::Version.new(matches[2])] + end + + # For consistency with other langauges, we define a requirements array. + # Go doesn't have an `OR` separator for requirements, so it always + # contains a single element. + def self.requirements_array(requirement_string) + [new(requirement_string)] + end + + def initialize(*requirements) + requirements = requirements.flatten.flat_map do |req_string| + req_string.split(",").map do |r| + convert_go_constraint_to_ruby_constraint(r.strip) + end + end + + super(requirements) + end + + private + + def convert_go_constraint_to_ruby_constraint(req_string) + req_string = req_string + req_string = convert_wildcard_characters(req_string) + + if req_string.match?(WILDCARD_REGEX) + ruby_range(req_string.gsub(WILDCARD_REGEX, "").gsub(/^[^\d]/, "")) + elsif req_string.match?(/^~[^>]/) then convert_tilde_req(req_string) + elsif req_string.match?(/^[\d^]/) then convert_caret_req(req_string) + elsif req_string.match?(/[<=>]/) then req_string + else ruby_range(req_string) + end + end + + # rubocop:disable Metrics/PerceivedComplexity + def convert_wildcard_characters(req_string) + return req_string unless req_string.match?(WILDCARD_REGEX) + + if req_string.start_with?(">", "^", "~") + req_string.split("."). + map { |p| p.match?(WILDCARD_REGEX) ? "0" : p }. + join(".") + elsif req_string.start_with?("<") + parts = req_string.split(".") + parts.map.with_index do |part, index| + next "0" if part.match?(WILDCARD_REGEX) + next part.to_i + 1 if parts[index + 1]&.match?(WILDCARD_REGEX) + part + end.join(".") + else + req_string + end + end + # rubocop:enable Metrics/PerceivedComplexity + + def convert_tilde_req(req_string) + version = req_string.gsub(/^~/, "") + parts = version.split(".") + parts << "0" if parts.count < 3 + "~> #{parts.join('.')}" + end + + def ruby_range(req_string) + parts = req_string.split(".") + + # If we have three or more parts then this is an exact match + return req_string if parts.count >= 3 + + # If we have no parts then the version is completely unlocked + return ">= 0" if parts.count.zero? + + # If we have fewer than three parts we do a partial match + parts << "0" + "~> #{parts.join('.')}" + end + + def convert_caret_req(req_string) + version = req_string.gsub(/^\^/, "") + parts = version.split(".") + first_non_zero = parts.find { |d| d != "0" } + first_non_zero_index = + first_non_zero ? parts.index(first_non_zero) : parts.count - 1 + upper_bound = parts.map.with_index do |part, i| + if i < first_non_zero_index then part + elsif i == first_non_zero_index then (part.to_i + 1).to_s + else 0 + end + end.join(".") + + [">= #{version}", "< #{upper_bound}"] + end + end + end + end +end diff --git a/lib/dependabot/utils/go/version.rb b/lib/dependabot/utils/go/version.rb new file mode 100644 index 00000000000..f40f7f4650f --- /dev/null +++ b/lib/dependabot/utils/go/version.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +# Go pre-release versions use 1.0.1-rc1 syntax, which Gem::Version +# converts into 1.0.1.pre.rc1. We override the `to_s` method to stop that +# alteration. +# Best docs are at https://github.com/Masterminds/semver + +module Dependabot + module Utils + module Go + class Version < Gem::Version + def initialize(version) + @version_string = version.to_s + super + end + + def to_s + @version_string + end + end + end + end +end diff --git a/spec/dependabot/utils/go/requirement_spec.rb b/spec/dependabot/utils/go/requirement_spec.rb new file mode 100644 index 00000000000..f3a18dc3616 --- /dev/null +++ b/spec/dependabot/utils/go/requirement_spec.rb @@ -0,0 +1,146 @@ +# frozen_string_literal: true + +require "spec_helper" +require "dependabot/utils/go/requirement" + +RSpec.describe Dependabot::Utils::Go::Requirement do + subject(:requirement) { described_class.new(requirement_string) } + let(:requirement_string) { ">=1.0.0" } + + describe ".new" do + it { is_expected.to be_a(described_class) } + + context "with a blank string" do + let(:requirement_string) { "" } + it { is_expected.to eq(described_class.new(">= 0")) } + end + + context "with a pre-release" do + let(:requirement_string) { "4.0.0-beta3" } + it "preserves the pre-release formatting" do + expect(requirement.requirements.first.last.to_s).to eq("4.0.0-beta3") + end + end + + describe "wildcards" do + context "with only a *" do + let(:requirement_string) { "*" } + it { is_expected.to eq(described_class.new(">= 0")) } + end + + context "with a 1.*" do + let(:requirement_string) { "1.*" } + it { is_expected.to eq(described_class.new("~> 1.0")) } + end + + context "with a 1.1.*" do + let(:requirement_string) { "1.1.*" } + it { is_expected.to eq(described_class.new("~> 1.1.0")) } + + context "prefixed with a caret" do + let(:requirement_string) { "^1.1.*" } + it { is_expected.to eq(described_class.new(">= 1.1.0", "< 2.0.0")) } + end + + context "prefixed with a ~" do + let(:requirement_string) { "~1.1.x" } + it { is_expected.to eq(described_class.new("~> 1.1.0")) } + end + + context "prefixed with a <" do + let(:requirement_string) { "<1.1.X" } + it { is_expected.to eq(described_class.new("< 1.2.0")) } + end + end + end + + context "with no specifier" do + let(:requirement_string) { "1.1.0" } + it { is_expected.to eq(described_class.new(">= 1.1.0", "< 2.0.0")) } + end + + context "with a caret version" do + context "specified to 3 dp" do + let(:requirement_string) { "^1.2.3" } + it { is_expected.to eq(described_class.new(">= 1.2.3", "< 2.0.0")) } + + context "with a zero major" do + let(:requirement_string) { "^0.2.3" } + it { is_expected.to eq(described_class.new(">= 0.2.3", "< 0.3.0")) } + + context "and a zero minor" do + let(:requirement_string) { "^0.0.3" } + it { is_expected.to eq(described_class.new(">= 0.0.3", "< 0.0.4")) } + end + end + end + + context "specified to 2 dp" do + let(:requirement_string) { "^1.2" } + it { is_expected.to eq(described_class.new(">= 1.2", "< 2.0")) } + + context "with a zero major" do + let(:requirement_string) { "^0.2" } + it { is_expected.to eq(described_class.new(">= 0.2", "< 0.3")) } + + context "and a zero minor" do + let(:requirement_string) { "^0.0" } + it { is_expected.to eq(described_class.new(">= 0.0", "< 0.1")) } + end + end + end + + context "specified to 1 dp" do + let(:requirement_string) { "^1" } + it { is_expected.to eq(described_class.new(">= 1", "< 2")) } + + context "with a zero major" do + let(:requirement_string) { "^0" } + it { is_expected.to eq(described_class.new(">= 0", "< 1")) } + end + end + end + + context "with a ~ version" do + context "specified to 3 dp" do + let(:requirement_string) { "~1.5.1" } + it { is_expected.to eq(described_class.new("~> 1.5.1")) } + end + + context "specified to 2 dp" do + let(:requirement_string) { "~1.5" } + it { is_expected.to eq(described_class.new("~> 1.5.0")) } + end + + context "specified to 1 dp" do + let(:requirement_string) { "~1" } + it { is_expected.to eq(described_class.new("~> 1.0")) } + end + end + + context "with a > version specified" do + let(:requirement_string) { ">1.5.1" } + it { is_expected.to eq(Gem::Requirement.new("> 1.5.1")) } + end + + context "with an = version specified" do + let(:requirement_string) { "=1.5" } + it { is_expected.to eq(Gem::Requirement.new("1.5")) } + end + + context "with a != version specified" do + let(:requirement_string) { "!=1.5" } + it { is_expected.to eq(Gem::Requirement.new("!=1.5")) } + end + + context "with an ~> version specified" do + let(:requirement_string) { "~> 1.5.1" } + it { is_expected.to eq(Gem::Requirement.new("~> 1.5.1")) } + end + + context "with a comma separated list" do + let(:requirement_string) { ">1.5.1, < 2.0.0" } + it { is_expected.to eq(Gem::Requirement.new("> 1.5.1", "< 2.0.0")) } + end + end +end diff --git a/spec/dependabot/utils/go/version_spec.rb b/spec/dependabot/utils/go/version_spec.rb new file mode 100644 index 00000000000..4740e4aba9c --- /dev/null +++ b/spec/dependabot/utils/go/version_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require "spec_helper" +require "dependabot/utils/go/version" + +RSpec.describe Dependabot::Utils::Go::Version do + subject(:version) { described_class.new(version_string) } + let(:version_string) { "1.0.0" } + + describe "#to_s" do + subject { version.to_s } + + context "with a non-prerelease" do + let(:version_string) { "1.0.0" } + it { is_expected.to eq "1.0.0" } + end + + context "with a normal prerelease" do + let(:version_string) { "1.0.0.pre1" } + it { is_expected.to eq "1.0.0.pre1" } + end + + context "with a PHP-style prerelease" do + let(:version_string) { "1.0.0-pre1" } + it { is_expected.to eq "1.0.0-pre1" } + end + end + + describe "compatibility with Gem::Requirement" do + subject { requirement.satisfied_by?(version) } + let(:requirement) { Gem::Requirement.new(">= 1.0.0") } + + context "with a valid version" do + let(:version_string) { "1.0.0" } + it { is_expected.to eq(true) } + end + + context "with an invalid version" do + let(:version_string) { "0.9.0" } + it { is_expected.to eq(false) } + end + + context "with a valid prerelease version" do + let(:version_string) { "1.1.0-pre" } + it { is_expected.to eq(true) } + end + end +end From ea8902dfe43bc30e9a91044274c80448b69f3f1f Mon Sep 17 00:00:00 2001 From: Grey Baker Date: Tue, 24 Jul 2018 12:53:30 +0100 Subject: [PATCH 19/28] Dep: Add support for hyphen ranges and || conditions --- lib/dependabot/utils/go/requirement.rb | 21 ++++++++------ spec/dependabot/utils/go/requirement_spec.rb | 29 ++++++++++++++++++++ 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/lib/dependabot/utils/go/requirement.rb b/lib/dependabot/utils/go/requirement.rb index 81d28e172a8..36b698c7595 100644 --- a/lib/dependabot/utils/go/requirement.rb +++ b/lib/dependabot/utils/go/requirement.rb @@ -13,6 +13,7 @@ module Utils module Go class Requirement < Gem::Requirement WILDCARD_REGEX = /(?:\.|^)[xX*]/ + OR_SEPARATOR = /(?<=[a-zA-Z0-9*])\s*\|{2}/ # Use Utils::Go::Version rather than Gem::Version to ensure that # pre-release versions aren't transformed. @@ -30,11 +31,13 @@ def self.parse(obj) [matches[1] || "=", Utils::Go::Version.new(matches[2])] end - # For consistency with other langauges, we define a requirements array. - # Go doesn't have an `OR` separator for requirements, so it always - # contains a single element. + # Returns an array of requirements. At least one requirement from the + # returned array must be satisfied for a version to be valid. def self.requirements_array(requirement_string) - [new(requirement_string)] + return [new(nil)] if requirement_string.nil? + requirement_string.strip.split(OR_SEPARATOR).map do |req_string| + new(req_string) + end end def initialize(*requirements) @@ -56,16 +59,14 @@ def convert_go_constraint_to_ruby_constraint(req_string) if req_string.match?(WILDCARD_REGEX) ruby_range(req_string.gsub(WILDCARD_REGEX, "").gsub(/^[^\d]/, "")) elsif req_string.match?(/^~[^>]/) then convert_tilde_req(req_string) + elsif req_string.include?(" - ") then convert_hyphen_req(req_string) elsif req_string.match?(/^[\d^]/) then convert_caret_req(req_string) elsif req_string.match?(/[<=>]/) then req_string else ruby_range(req_string) end end - # rubocop:disable Metrics/PerceivedComplexity def convert_wildcard_characters(req_string) - return req_string unless req_string.match?(WILDCARD_REGEX) - if req_string.start_with?(">", "^", "~") req_string.split("."). map { |p| p.match?(WILDCARD_REGEX) ? "0" : p }. @@ -81,7 +82,6 @@ def convert_wildcard_characters(req_string) req_string end end - # rubocop:enable Metrics/PerceivedComplexity def convert_tilde_req(req_string) version = req_string.gsub(/^~/, "") @@ -90,6 +90,11 @@ def convert_tilde_req(req_string) "~> #{parts.join('.')}" end + def convert_hyphen_req(req_string) + lower_bound, upper_bound = req_string.split(/\s+-\s+/) + [">= #{lower_bound}", "<= #{upper_bound}"] + end + def ruby_range(req_string) parts = req_string.split(".") diff --git a/spec/dependabot/utils/go/requirement_spec.rb b/spec/dependabot/utils/go/requirement_spec.rb index f3a18dc3616..2be6aa9951b 100644 --- a/spec/dependabot/utils/go/requirement_spec.rb +++ b/spec/dependabot/utils/go/requirement_spec.rb @@ -123,6 +123,11 @@ it { is_expected.to eq(Gem::Requirement.new("> 1.5.1")) } end + context "with a range literal specified" do + let(:requirement_string) { "1.1.1 - 1.2.0" } + it { is_expected.to eq(Gem::Requirement.new(">= 1.1.1", "<= 1.2.0")) } + end + context "with an = version specified" do let(:requirement_string) { "=1.5" } it { is_expected.to eq(Gem::Requirement.new("1.5")) } @@ -143,4 +148,28 @@ it { is_expected.to eq(Gem::Requirement.new("> 1.5.1", "< 2.0.0")) } end end + + describe ".requirements_array" do + subject(:requirements) do + described_class.requirements_array(requirement_string) + end + + context "with a single requirement" do + let(:requirement_string) { ">=1.0.0" } + it { is_expected.to eq([described_class.new(">= 1.0.0")]) } + end + + context "with an OR requirement" do + let(:requirement_string) { "^1.1.0 || ^2.1.0" } + + it "returns an array of requirements" do + expect(requirements).to match_array( + [ + described_class.new(">= 1.1.0", "< 2.0.0"), + described_class.new(">= 2.1.0", "< 3.0.0") + ] + ) + end + end + end end From 2475dc0276c71614e63562b8bbee1ea2b20699d9 Mon Sep 17 00:00:00 2001 From: Grey Baker Date: Tue, 24 Jul 2018 14:52:08 +0100 Subject: [PATCH 20/28] Dep: Allow v-prefixed versions --- lib/dependabot/utils/go/requirement.rb | 7 +++++ lib/dependabot/utils/go/version.rb | 6 ++++ spec/dependabot/utils/go/requirement_spec.rb | 5 ++++ spec/dependabot/utils/go/version_spec.rb | 31 ++++++++++++++++++++ 4 files changed, 49 insertions(+) diff --git a/lib/dependabot/utils/go/requirement.rb b/lib/dependabot/utils/go/requirement.rb index 36b698c7595..a6c8a0b10ea 100644 --- a/lib/dependabot/utils/go/requirement.rb +++ b/lib/dependabot/utils/go/requirement.rb @@ -15,6 +15,13 @@ class Requirement < Gem::Requirement WILDCARD_REGEX = /(?:\.|^)[xX*]/ OR_SEPARATOR = /(?<=[a-zA-Z0-9*])\s*\|{2}/ + # Override the version pattern to allow a 'v' prefix + quoted = OPS.keys.map { |k| Regexp.quote(k) }.join("|") + version_pattern = "v?#{Gem::Version::VERSION_PATTERN}" + + PATTERN_RAW = "\\s*(#{quoted})?\\s*(#{version_pattern})\\s*" + PATTERN = /\A#{PATTERN_RAW}\z/ + # Use Utils::Go::Version rather than Gem::Version to ensure that # pre-release versions aren't transformed. def self.parse(obj) diff --git a/lib/dependabot/utils/go/version.rb b/lib/dependabot/utils/go/version.rb index f40f7f4650f..b7ac7d12013 100644 --- a/lib/dependabot/utils/go/version.rb +++ b/lib/dependabot/utils/go/version.rb @@ -9,8 +9,14 @@ module Dependabot module Utils module Go class Version < Gem::Version + def self.correct?(version) + version = version.gsub(/^v/, "") if version.is_a?(String) + super(version) + end + def initialize(version) @version_string = version.to_s + version = version.gsub(/^v/, "") if version.is_a?(String) super end diff --git a/spec/dependabot/utils/go/requirement_spec.rb b/spec/dependabot/utils/go/requirement_spec.rb index 2be6aa9951b..a7369bee7ac 100644 --- a/spec/dependabot/utils/go/requirement_spec.rb +++ b/spec/dependabot/utils/go/requirement_spec.rb @@ -15,6 +15,11 @@ it { is_expected.to eq(described_class.new(">= 0")) } end + context "with a 'v' prefix" do + let(:requirement_string) { ">=v1.0.0" } + it { is_expected.to eq(described_class.new(">= v1.0.0")) } + end + context "with a pre-release" do let(:requirement_string) { "4.0.0-beta3" } it "preserves the pre-release formatting" do diff --git a/spec/dependabot/utils/go/version_spec.rb b/spec/dependabot/utils/go/version_spec.rb index 4740e4aba9c..c1776c456cf 100644 --- a/spec/dependabot/utils/go/version_spec.rb +++ b/spec/dependabot/utils/go/version_spec.rb @@ -7,6 +7,25 @@ subject(:version) { described_class.new(version_string) } let(:version_string) { "1.0.0" } + describe ".correct?" do + subject { described_class.correct?(version_string) } + + context "with a string prefixed with a 'v'" do + let(:version_string) { "v1.0.0" } + it { is_expected.to eq(true) } + end + + context "with a string not prefixed with a 'v'" do + let(:version_string) { "1.0.0" } + it { is_expected.to eq(true) } + end + + context "with an invalid string" do + let(:version_string) { "va1.0.0" } + it { is_expected.to eq(false) } + end + end + describe "#to_s" do subject { version.to_s } @@ -44,5 +63,17 @@ let(:version_string) { "1.1.0-pre" } it { is_expected.to eq(true) } end + + context "prefixed with a 'v'" do + context "with a greater version" do + let(:version_string) { "v1.1.0" } + it { is_expected.to eq(true) } + end + + context "with an lesser version" do + let(:version_string) { "v0.9.0" } + it { is_expected.to eq(false) } + end + end end end From 42ca43c9456997a4567b22088bb863ff6a3c4707 Mon Sep 17 00:00:00 2001 From: Grey Baker Date: Tue, 24 Jul 2018 18:28:23 +0100 Subject: [PATCH 21/28] Dep: Handle v-prefixes correctly in requirement class --- .../update_checkers/rust/cargo/requirements_updater.rb | 2 +- lib/dependabot/utils/go/requirement.rb | 4 ++-- spec/dependabot/utils/go/requirement_spec.rb | 5 +++++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/dependabot/update_checkers/rust/cargo/requirements_updater.rb b/lib/dependabot/update_checkers/rust/cargo/requirements_updater.rb index 4e1f1b7c915..59b7608d0f4 100644 --- a/lib/dependabot/update_checkers/rust/cargo/requirements_updater.rb +++ b/lib/dependabot/update_checkers/rust/cargo/requirements_updater.rb @@ -94,7 +94,7 @@ def updated_library_requirement(req) return req if ruby_reqs.all? { |r| r.satisfied_by?(target_version) } # TODO: In future, we might want to treat libraries differently to - # applications. Fo now, since Rust allows multiple versions of the + # applications. For now, since Rust allows multiple versions of the # same dependeny, it's fine to upgrade them like apps if required updated_app_requirement(req) end diff --git a/lib/dependabot/utils/go/requirement.rb b/lib/dependabot/utils/go/requirement.rb index a6c8a0b10ea..a3a57e3bf27 100644 --- a/lib/dependabot/utils/go/requirement.rb +++ b/lib/dependabot/utils/go/requirement.rb @@ -67,7 +67,7 @@ def convert_go_constraint_to_ruby_constraint(req_string) ruby_range(req_string.gsub(WILDCARD_REGEX, "").gsub(/^[^\d]/, "")) elsif req_string.match?(/^~[^>]/) then convert_tilde_req(req_string) elsif req_string.include?(" - ") then convert_hyphen_req(req_string) - elsif req_string.match?(/^[\d^]/) then convert_caret_req(req_string) + elsif req_string.match?(/^[\dv^]/) then convert_caret_req(req_string) elsif req_string.match?(/[<=>]/) then req_string else ruby_range(req_string) end @@ -117,7 +117,7 @@ def ruby_range(req_string) end def convert_caret_req(req_string) - version = req_string.gsub(/^\^/, "") + version = req_string.gsub(/^\^?v?/, "") parts = version.split(".") first_non_zero = parts.find { |d| d != "0" } first_non_zero_index = diff --git a/spec/dependabot/utils/go/requirement_spec.rb b/spec/dependabot/utils/go/requirement_spec.rb index a7369bee7ac..52ed3f069e8 100644 --- a/spec/dependabot/utils/go/requirement_spec.rb +++ b/spec/dependabot/utils/go/requirement_spec.rb @@ -62,6 +62,11 @@ context "with no specifier" do let(:requirement_string) { "1.1.0" } it { is_expected.to eq(described_class.new(">= 1.1.0", "< 2.0.0")) } + + context "and a v prefix" do + let(:requirement_string) { "v1.1.0" } + it { is_expected.to eq(described_class.new(">= 1.1.0", "< 2.0.0")) } + end end context "with a caret version" do From f46196306a7be2ee3e1f276947a2595fa06a65c2 Mon Sep 17 00:00:00 2001 From: Grey Baker Date: Tue, 24 Jul 2018 19:42:34 +0100 Subject: [PATCH 22/28] Dep: Update caret notation implementation to no special case pre-1.0.0 dependencies --- lib/dependabot/utils/go/requirement.rb | 12 +++------ spec/dependabot/utils/go/requirement_spec.rb | 28 +++++++++++--------- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/lib/dependabot/utils/go/requirement.rb b/lib/dependabot/utils/go/requirement.rb index a3a57e3bf27..3d1c3625ae9 100644 --- a/lib/dependabot/utils/go/requirement.rb +++ b/lib/dependabot/utils/go/requirement.rb @@ -116,18 +116,12 @@ def ruby_range(req_string) "~> #{parts.join('.')}" end + # Note: Dep's caret notation implementation doesn't distinguish between + # pre and post-1.0.0 requirements (unlike in JS) def convert_caret_req(req_string) version = req_string.gsub(/^\^?v?/, "") parts = version.split(".") - first_non_zero = parts.find { |d| d != "0" } - first_non_zero_index = - first_non_zero ? parts.index(first_non_zero) : parts.count - 1 - upper_bound = parts.map.with_index do |part, i| - if i < first_non_zero_index then part - elsif i == first_non_zero_index then (part.to_i + 1).to_s - else 0 - end - end.join(".") + upper_bound = [parts.first.to_i + 1, 0, 0, "a"].map(&:to_s).join(".") [">= #{version}", "< #{upper_bound}"] end diff --git a/spec/dependabot/utils/go/requirement_spec.rb b/spec/dependabot/utils/go/requirement_spec.rb index 52ed3f069e8..ecfcd2239b8 100644 --- a/spec/dependabot/utils/go/requirement_spec.rb +++ b/spec/dependabot/utils/go/requirement_spec.rb @@ -44,7 +44,7 @@ context "prefixed with a caret" do let(:requirement_string) { "^1.1.*" } - it { is_expected.to eq(described_class.new(">= 1.1.0", "< 2.0.0")) } + it { is_expected.to eq(described_class.new(">= 1.1.0", "< 2.0.0.a")) } end context "prefixed with a ~" do @@ -61,52 +61,54 @@ context "with no specifier" do let(:requirement_string) { "1.1.0" } - it { is_expected.to eq(described_class.new(">= 1.1.0", "< 2.0.0")) } + it { is_expected.to eq(described_class.new(">= 1.1.0", "< 2.0.0.a")) } context "and a v prefix" do let(:requirement_string) { "v1.1.0" } - it { is_expected.to eq(described_class.new(">= 1.1.0", "< 2.0.0")) } + it { is_expected.to eq(described_class.new(">= 1.1.0", "< 2.0.0.a")) } end end context "with a caret version" do context "specified to 3 dp" do let(:requirement_string) { "^1.2.3" } - it { is_expected.to eq(described_class.new(">= 1.2.3", "< 2.0.0")) } + it { is_expected.to eq(described_class.new(">= 1.2.3", "< 2.0.0.a")) } context "with a zero major" do let(:requirement_string) { "^0.2.3" } - it { is_expected.to eq(described_class.new(">= 0.2.3", "< 0.3.0")) } + it { is_expected.to eq(described_class.new(">= 0.2.3", "< 1.0.0.a")) } context "and a zero minor" do let(:requirement_string) { "^0.0.3" } - it { is_expected.to eq(described_class.new(">= 0.0.3", "< 0.0.4")) } + it do + is_expected.to eq(described_class.new(">= 0.0.3", "< 1.0.0.a")) + end end end end context "specified to 2 dp" do let(:requirement_string) { "^1.2" } - it { is_expected.to eq(described_class.new(">= 1.2", "< 2.0")) } + it { is_expected.to eq(described_class.new(">= 1.2", "< 2.0.0.a")) } context "with a zero major" do let(:requirement_string) { "^0.2" } - it { is_expected.to eq(described_class.new(">= 0.2", "< 0.3")) } + it { is_expected.to eq(described_class.new(">= 0.2", "< 1.0.0.a")) } context "and a zero minor" do let(:requirement_string) { "^0.0" } - it { is_expected.to eq(described_class.new(">= 0.0", "< 0.1")) } + it { is_expected.to eq(described_class.new(">= 0.0", "< 1.0.0.a")) } end end end context "specified to 1 dp" do let(:requirement_string) { "^1" } - it { is_expected.to eq(described_class.new(">= 1", "< 2")) } + it { is_expected.to eq(described_class.new(">= 1", "< 2.0.0.a")) } context "with a zero major" do let(:requirement_string) { "^0" } - it { is_expected.to eq(described_class.new(">= 0", "< 1")) } + it { is_expected.to eq(described_class.new(">= 0", "< 1.0.0.a")) } end end end @@ -175,8 +177,8 @@ it "returns an array of requirements" do expect(requirements).to match_array( [ - described_class.new(">= 1.1.0", "< 2.0.0"), - described_class.new(">= 2.1.0", "< 3.0.0") + described_class.new(">= 1.1.0", "< 2.0.0.a"), + described_class.new(">= 2.1.0", "< 3.0.0.a") ] ) end From 75eee7dee9f91bf1ac85c595ca453538336f8189 Mon Sep 17 00:00:00 2001 From: Grey Baker Date: Tue, 24 Jul 2018 23:27:09 +0100 Subject: [PATCH 23/28] Dep: Add requirements updater (currently only works for library-style updates) --- .../go/dep/requirements_updater.rb | 174 +++++++++ lib/dependabot/utils/go/requirement.rb | 26 +- .../go/dep/requirements_updater_spec.rb | 362 ++++++++++++++++++ spec/dependabot/utils/go/requirement_spec.rb | 40 +- 4 files changed, 596 insertions(+), 6 deletions(-) create mode 100644 lib/dependabot/update_checkers/go/dep/requirements_updater.rb create mode 100644 spec/dependabot/update_checkers/go/dep/requirements_updater_spec.rb diff --git a/lib/dependabot/update_checkers/go/dep/requirements_updater.rb b/lib/dependabot/update_checkers/go/dep/requirements_updater.rb new file mode 100644 index 00000000000..5b9f9741747 --- /dev/null +++ b/lib/dependabot/update_checkers/go/dep/requirements_updater.rb @@ -0,0 +1,174 @@ +# frozen_string_literal: true + +require "dependabot/update_checkers/go/dep" +require "dependabot/utils/go/requirement" +require "dependabot/utils/go/version" + +module Dependabot + module UpdateCheckers + module Go + class Dep + class RequirementsUpdater + class UnfixableRequirement < StandardError; end + + VERSION_REGEX = /[0-9]+(?:\.[A-Za-z0-9\-*]+)*/ + + def initialize(requirements:, library:, updated_source:, + latest_version:, latest_resolvable_version:) + @requirements = requirements + @library = library + @updated_source = updated_source + + if latest_version && version_class.correct?(latest_version) + @latest_version = version_class.new(latest_version) + end + + return unless latest_resolvable_version + return unless version_class.correct?(latest_resolvable_version) + @latest_resolvable_version = + version_class.new(latest_resolvable_version) + end + + def updated_requirements + requirements.map do |req| + req = req.merge(source: updated_source) + next req unless latest_resolvable_version + next initial_req_after_source_change(req) unless req[:requirement] + + # In future we'll write logic to update apps differently, but + # for now we can't tell them apart + updated_library_requirement(req) + end + end + + private + + attr_reader :requirements, :updated_source, + :latest_version, :latest_resolvable_version + + def library? + @library + end + + def updating_from_git_to_version? + return false unless updated_source.nil? + original_source = requirements.map { |r| r[:source] }.compact.first + original_source&.fetch(:type) == "git" + end + + def initial_req_after_source_change(req) + return req unless updating_from_git_to_version? + return req unless req[:requirement].nil? + req.merge(requirement: "^#{latest_resolvable_version}") + end + + def updated_library_requirement(req) + current_requirement = req[:requirement] + version = latest_resolvable_version + + ruby_reqs = ruby_requirements(current_requirement) + return req if ruby_reqs.any? { |r| r.satisfied_by?(version) } + + reqs = current_requirement.strip.split(",").map(&:strip) + + updated_requirement = + if current_requirement.include?("||") + # Further widen the range by adding another OR condition + current_requirement + " || ^#{version}" + elsif reqs.any? { |r| r.match?(/(<|-\s)/) } + # Further widen the range by updating the upper bound + update_range_requirement(current_requirement) + else + # Convert existing requirement to a range + create_new_range_requirement(reqs) + end + + req.merge(requirement: updated_requirement) + end + + def ruby_requirements(requirement_string) + requirement_class.requirements_array(requirement_string) + end + + def update_range_requirement(req_string) + range_requirement = req_string.split(","). + find { |r| r.match?(/<|(\s+-\s+)/) } + + versions = range_requirement.scan(VERSION_REGEX) + upper_bound = versions.map { |v| version_class.new(v) }.max + new_upper_bound = update_greatest_version( + upper_bound, + latest_resolvable_version + ) + + req_string.sub( + upper_bound.to_s, + new_upper_bound.to_s + ) + end + + def create_new_range_requirement(string_reqs) + version = latest_resolvable_version + + lower_bound = + string_reqs. + map { |req| requirement_class.new(req) }. + flat_map { |req| req.requirements.map(&:last) }. + min.to_s + + upper_bound = + if string_reqs.first.start_with?("~") && + version.to_s.split(".").count > 1 + create_upper_bound_for_tilda_req(string_reqs.first) + else + upper_bound_parts = [version.to_s.split(".").first.to_i + 1] + upper_bound_parts. + fill("0", 1..(lower_bound.split(".").count - 1)). + join(".") + end + + ">= #{lower_bound}, < #{upper_bound}" + end + + def create_upper_bound_for_tilda_req(string_req) + tilda_version = requirement_class.new(string_req). + requirements.map(&:last). + min.to_s + + upper_bound_parts = latest_resolvable_version.to_s.split(".") + upper_bound_parts.slice(0, tilda_version.to_s.split(".").count) + upper_bound_parts[-1] = "0" + upper_bound_parts[-2] = (upper_bound_parts[-2].to_i + 1).to_s + + upper_bound_parts.join(".") + end + + def update_greatest_version(old_version, version_to_be_permitted) + version = version_class.new(old_version) + version = version.release if version.prerelease? + + index_to_update = + version.segments.map.with_index { |seg, i| seg.zero? ? 0 : i }.max + + version.segments.map.with_index do |_, index| + if index < index_to_update + version_to_be_permitted.segments[index] + elsif index == index_to_update + version_to_be_permitted.segments[index] + 1 + else 0 + end + end.join(".") + end + + def version_class + Utils::Go::Version + end + + def requirement_class + Utils::Go::Requirement + end + end + end + end + end +end diff --git a/lib/dependabot/utils/go/requirement.rb b/lib/dependabot/utils/go/requirement.rb index 3d1c3625ae9..5dc7177dcff 100644 --- a/lib/dependabot/utils/go/requirement.rb +++ b/lib/dependabot/utils/go/requirement.rb @@ -74,10 +74,8 @@ def convert_go_constraint_to_ruby_constraint(req_string) end def convert_wildcard_characters(req_string) - if req_string.start_with?(">", "^", "~") - req_string.split("."). - map { |p| p.match?(WILDCARD_REGEX) ? "0" : p }. - join(".") + if req_string.match?(/^[\dv^>~]/) + replace_wildcard_in_lower_bound(req_string) elsif req_string.start_with?("<") parts = req_string.split(".") parts.map.with_index do |part, index| @@ -90,6 +88,26 @@ def convert_wildcard_characters(req_string) end end + def replace_wildcard_in_lower_bound(req_string) + after_wildcard = false + + if req_string.start_with?("~") + req_string = req_string.gsub(/(?:(?:\.|^)[xX*])(\.[xX*])+/, "") + end + + req_string.split("."). + map do |part| + part.split("-").map.with_index do |p, i| + # Before we hit a wildcard we just return the existing part + next p unless p.match?(WILDCARD_REGEX) || after_wildcard + + # On or after a wildcard we replace the version part with zero + after_wildcard = true + i.zero? ? "0" : "a" + end.join("-") + end.join(".") + end + def convert_tilde_req(req_string) version = req_string.gsub(/^~/, "") parts = version.split(".") diff --git a/spec/dependabot/update_checkers/go/dep/requirements_updater_spec.rb b/spec/dependabot/update_checkers/go/dep/requirements_updater_spec.rb new file mode 100644 index 00000000000..aa5568b43e1 --- /dev/null +++ b/spec/dependabot/update_checkers/go/dep/requirements_updater_spec.rb @@ -0,0 +1,362 @@ +# frozen_string_literal: true + +require "spec_helper" +require "dependabot/update_checkers/go/dep/requirements_updater" + +RSpec.describe Dependabot::UpdateCheckers::Go::Dep::RequirementsUpdater do + let(:updater) do + described_class.new( + requirements: requirements, + updated_source: updated_source, + library: library, + latest_version: latest_version, + latest_resolvable_version: latest_resolvable_version + ) + end + + let(:requirements) { [manifest_req] } + let(:updated_source) { nil } + let(:manifest_req) do + { + file: "Gopkg.toml", + requirement: manifest_req_string, + groups: [], + source: nil + } + end + let(:manifest_req_string) { "^1.4.0" } + + let(:library) { false } + let(:latest_version) { "1.8.0" } + let(:latest_resolvable_version) { "1.5.0" } + let(:version_class) { Dependabot::Utils::Go::Version } + + describe "#updated_requirements" do + subject { updater.updated_requirements.first } + + specify { expect(updater.updated_requirements.count).to eq(1) } + + let(:manifest_req_string) { "^1.0.0" } + let(:latest_resolvable_version) { nil } + + context "when there is no resolvable version" do + let(:latest_resolvable_version) { nil } + its([:requirement]) { is_expected.to eq(manifest_req_string) } + end + + context "with a git dependency" do + let(:latest_resolvable_version) { Gem::Version.new("1.5.0") } + let(:manifest_req) do + { + file: "Gopkg.toml", + requirement: manifest_req_string, + groups: [], + source: { + type: "git", + url: "https://github.com/jonschlinkert/is-number", + branch: nil, + ref: "2.0.0" + } + } + end + let(:updated_source) do + { + type: "git", + url: "https://github.com/jonschlinkert/is-number", + branch: nil, + ref: "2.1.0" + } + end + + context "with no requirement" do + let(:manifest_req_string) { nil } + + it "updates the source" do + expect(updater.updated_requirements). + to eq( + [{ + file: "Gopkg.toml", + requirement: nil, + groups: [], + source: { + type: "git", + url: "https://github.com/jonschlinkert/is-number", + branch: nil, + ref: "2.1.0" + } + }] + ) + end + + context "updating to use npm" do + let(:updated_source) { nil } + + it "updates the source and requirement" do + expect(updater.updated_requirements). + to eq( + [{ + file: "Gopkg.toml", + requirement: "^1.5.0", + groups: [], + source: nil + }] + ) + end + end + end + end + + context "for a library requirement" do + let(:library) { true } + + context "when there is a resolvable version" do + let(:latest_resolvable_version) { Gem::Version.new("1.5.0") } + + context "and a full version was previously specified" do + let(:manifest_req_string) { "1.2.3" } + its([:requirement]) { is_expected.to eq("1.2.3") } + + context "that needs to be updated" do + let(:manifest_req_string) { "0.1.3" } + its([:requirement]) { is_expected.to eq(">= 0.1.3, < 2.0.0") } + end + end + + context "and v-prefix was previously used" do + let(:manifest_req_string) { "v1.2.3" } + its([:requirement]) { is_expected.to eq("v1.2.3") } + + context "that needs to be updated" do + let(:manifest_req_string) { "v0.1.3" } + its([:requirement]) { is_expected.to eq(">= 0.1.3, < 2.0.0") } + end + end + + context "and a partial version was previously specified" do + let(:manifest_req_string) { "0.1" } + its([:requirement]) { is_expected.to eq(">= 0.1, < 2.0") } + end + + context "and only the major part was previously specified" do + let(:manifest_req_string) { "1" } + let(:latest_resolvable_version) { Gem::Version.new("4.5.0") } + its([:requirement]) { is_expected.to eq(">= 1, < 5") } + end + + context "and the new version has fewer digits than the old one" do + let(:manifest_req_string) { "1.1.0.1" } + its([:requirement]) { is_expected.to eq("1.1.0.1") } + end + + context "and the new version has much fewer digits than the old one" do + let(:manifest_req_string) { "1.1.0.1" } + let(:latest_resolvable_version) { Gem::Version.new("4") } + its([:requirement]) { is_expected.to eq(">= 1.1.0.1, < 5.0.0.0") } + end + + context "with a < condition" do + let(:manifest_req_string) { "< 1.2.0" } + its([:requirement]) { is_expected.to eq("< 1.6.0") } + end + + context "and a - was previously specified" do + let(:manifest_req_string) { "1.2.3 - 1.4.0" } + its([:requirement]) { is_expected.to eq("1.2.3 - 1.6.0") } + + context "with a pre-release version" do + let(:manifest_req_string) { "1.2.3-rc1 - 1.4.0" } + its([:requirement]) { is_expected.to eq("1.2.3-rc1 - 1.6.0") } + end + end + + context "and a pre-release was previously specified" do + let(:manifest_req_string) { "1.2.3-rc1" } + its([:requirement]) { is_expected.to eq("1.2.3-rc1") } + + context "when the version needs updating" do + let(:latest_resolvable_version) { Gem::Version.new("2.5.0") } + its([:requirement]) { is_expected.to eq(">= 1.2.3-rc1, < 3.0.0") } + end + end + + context "and a caret was previously specified" do + context "that the latest version satisfies" do + let(:manifest_req_string) { "^1.2.3" } + its([:requirement]) { is_expected.to eq("^1.2.3") } + end + + context "that the latest version does not satisfy" do + let(:manifest_req_string) { "^0.8.0" } + its([:requirement]) { is_expected.to eq(">= 0.8.0, < 2.0.0") } + end + + context "including a pre-release" do + let(:manifest_req_string) { "^1.2.3-rc1" } + its([:requirement]) { is_expected.to eq("^1.2.3-rc1") } + end + + context "updating to a pre-release of a new major version" do + let(:manifest_req_string) { "^1.0.0-beta1" } + let(:latest_resolvable_version) { version_class.new("2.0.0-alpha") } + its([:requirement]) do + is_expected.to eq(">= 1.0.0-beta1, < 3.0.0") + end + end + + context "including an x" do + let(:latest_resolvable_version) { Gem::Version.new("0.0.2") } + let(:manifest_req_string) { "^0.0.x" } + its([:requirement]) { is_expected.to eq("^0.0.x") } + + context "when the range isn't covered" do + let(:latest_resolvable_version) { Gem::Version.new("1.2.0") } + its([:requirement]) { is_expected.to eq(">= 0.0.0, < 2.0.0") } + end + end + + context "on a version that is all zeros" do + let(:latest_resolvable_version) { Gem::Version.new("0.0.2") } + let(:manifest_req_string) { "^0.0.0" } + its([:requirement]) { is_expected.to eq("^0.0.0") } + end + end + + context "and an x.x was previously specified" do + let(:manifest_req_string) { "0.x.x" } + its([:requirement]) { is_expected.to eq(">= 0.0.0, < 2.0.0") } + + context "four places" do + let(:manifest_req_string) { "0.x.x-rc1" } + its([:requirement]) { is_expected.to eq(">= 0.0.0-a, < 2.0.0") } + end + end + + context "with just *" do + let(:manifest_req_string) { "*" } + its([:requirement]) { is_expected.to eq("*") } + end + + context "and a tilda was previously specified" do + let(:latest_resolvable_version) { Gem::Version.new("2.5.3") } + + context "that the latest version satisfies" do + let(:manifest_req_string) { "~2.5.1" } + its([:requirement]) { is_expected.to eq("~2.5.1") } + end + + context "that the latest version does not satisfy" do + let(:manifest_req_string) { "~2.4.1" } + its([:requirement]) { is_expected.to eq(">= 2.4.1, < 2.6.0") } + end + + context "including a pre-release" do + let(:manifest_req_string) { "~2.5.1-rc1" } + its([:requirement]) { is_expected.to eq("~2.5.1-rc1") } + end + + context "including an x" do + let(:manifest_req_string) { "~2.x.x" } + its([:requirement]) { is_expected.to eq("~2.x.x") } + + context "when the range isn't covered" do + let(:manifest_req_string) { "~2.4.x" } + its([:requirement]) { is_expected.to eq(">= 2.4.0, < 2.6.0") } + end + end + end + + context "and there were multiple specifications" do + let(:manifest_req_string) { "> 1.0.0, < 1.2.0" } + its([:requirement]) { is_expected.to eq("> 1.0.0, < 1.6.0") } + + context "already valid" do + let(:manifest_req_string) { "> 1.0.0, < 1.7.0" } + its([:requirement]) { is_expected.to eq(manifest_req_string) } + end + + context "specified with || and valid" do + let(:manifest_req_string) { "^1.0.0 || ^2.0.0" } + its([:requirement]) { is_expected.to eq(manifest_req_string) } + end + + context "that include a pre-release" do + let(:manifest_req_string) { ">=1.2.0, <1.4.0-dev" } + its([:requirement]) { is_expected.to eq(">=1.2.0, <1.6.0") } + end + end + + context "and there were multiple requirements" do + let(:requirements) { [manifest_req, other_manifest_req] } + + let(:other_manifest_req) do + { + file: "another/Gopkg.toml", + requirement: other_requirement_string, + groups: [], + source: nil + } + end + let(:manifest_req_string) { "^1.2.3" } + let(:other_requirement_string) { "^0.x.x" } + + it "updates the requirement that needs to be updated" do + expect(updater.updated_requirements).to match_array( + [ + { + file: "Gopkg.toml", + requirement: "^1.2.3", + groups: [], + source: nil + }, + { + file: "another/Gopkg.toml", + requirement: ">= 0.0.0, < 2.0.0", + groups: [], + source: nil + } + ] + ) + end + + context "for the same file" do + let(:requirements) do + [ + { + requirement: "0.1.x", + file: "Gopkg.toml", + groups: ["dependencies"], + source: nil + }, + { + requirement: "^0.1.0", + file: "Gopkg.toml", + groups: ["devDependencies"], + source: nil + } + ] + end + + it "updates both requirements" do + expect(updater.updated_requirements).to match_array( + [ + { + requirement: ">= 0.1.0, < 2.0.0", + file: "Gopkg.toml", + groups: ["dependencies"], + source: nil + }, + { + requirement: ">= 0.1.0, < 2.0.0", + file: "Gopkg.toml", + groups: ["devDependencies"], + source: nil + } + ] + ) + end + end + end + end + end + end +end diff --git a/spec/dependabot/utils/go/requirement_spec.rb b/spec/dependabot/utils/go/requirement_spec.rb index ecfcd2239b8..3d9dcc2ad3a 100644 --- a/spec/dependabot/utils/go/requirement_spec.rb +++ b/spec/dependabot/utils/go/requirement_spec.rb @@ -35,21 +35,57 @@ context "with a 1.*" do let(:requirement_string) { "1.*" } - it { is_expected.to eq(described_class.new("~> 1.0")) } + it { is_expected.to eq(described_class.new(">= 1.0, < 2.0.0.a")) } + + context "with two wildcards" do + let(:requirement_string) { "1.*.*" } + it { is_expected.to eq(described_class.new(">= 1.0.0, < 2.0.0.a")) } + end + + context "for a pre-1.0.0 release" do + let(:requirement_string) { "0.*" } + it { is_expected.to eq(described_class.new(">= 0.0, < 1.0.0.a")) } + end + end + + context "with a 1.*.1" do + let(:requirement_string) { "1.*.1" } + it { is_expected.to eq(described_class.new(">= 1.0.0, < 2.0.0.a")) } end context "with a 1.1.*" do let(:requirement_string) { "1.1.*" } - it { is_expected.to eq(described_class.new("~> 1.1.0")) } + it { is_expected.to eq(described_class.new(">= 1.1.0", "< 2.0.0.a")) } context "prefixed with a caret" do let(:requirement_string) { "^1.1.*" } it { is_expected.to eq(described_class.new(">= 1.1.0", "< 2.0.0.a")) } + + context "for a pre-1.0.0 release" do + let(:requirement_string) { "^0.0.*" } + it do + is_expected.to eq(described_class.new(">= 0.0.0", "< 1.0.0.a")) + end + + context "with a pre-release specifier" do + let(:requirement_string) { "^0.0.*-alpha" } + + it "maintains a pre-release specifier" do + expect(requirement). + to eq(described_class.new(">= 0.0.0-a", "< 1.0.0.a")) + end + end + end end context "prefixed with a ~" do let(:requirement_string) { "~1.1.x" } it { is_expected.to eq(described_class.new("~> 1.1.0")) } + + context "with two wildcards" do + let(:requirement_string) { "~1.x.x" } + it { is_expected.to eq(described_class.new("~> 1.0")) } + end end context "prefixed with a <" do From db89e4f629cbdff2a67c987a64657975d9034ba0 Mon Sep 17 00:00:00 2001 From: Grey Baker Date: Wed, 25 Jul 2018 09:05:27 +0100 Subject: [PATCH 24/28] Dep: Hook up requirements updater with update checker --- lib/dependabot/update_checkers/go/dep.rb | 70 +++++++++- .../go/dep/requirements_updater.rb | 9 +- spec/dependabot/file_parsers/go/dep_spec.rb | 8 +- .../go/dep/file_preparer_spec.rb | 24 ++-- .../go/dep/requirements_updater_spec.rb | 20 +-- .../go/dep/version_resolver_spec.rb | 10 +- .../dependabot/update_checkers/go/dep_spec.rb | 130 +++++++++++++++--- spec/fixtures/git/upload_packs/jwt-go | Bin 0 -> 12105 bytes .../fixtures/go/gopkg_locks/bare_version.lock | 23 +--- spec/fixtures/go/gopkg_locks/no_version.lock | 23 +--- .../fixtures/go/gopkg_tomls/bare_version.toml | 7 +- spec/fixtures/go/gopkg_tomls/no_version.toml | 5 +- 12 files changed, 227 insertions(+), 102 deletions(-) create mode 100644 spec/fixtures/git/upload_packs/jwt-go diff --git a/lib/dependabot/update_checkers/go/dep.rb b/lib/dependabot/update_checkers/go/dep.rb index 6e8d715afa9..31d35d00499 100644 --- a/lib/dependabot/update_checkers/go/dep.rb +++ b/lib/dependabot/update_checkers/go/dep.rb @@ -8,6 +8,7 @@ module Go class Dep < Dependabot::UpdateCheckers::Base require_relative "dep/file_preparer" require_relative "dep/latest_version_finder" + require_relative "dep/requirements_updater" require_relative "dep/version_resolver" def latest_version @@ -39,11 +40,13 @@ def latest_resolvable_version_with_no_unlock end def updated_requirements - # If the dependency file needs to be updated we store the updated - # requirements on the dependency. - # - # TODO! - dependency.requirements + @updated_requirements ||= + RequirementsUpdater.new( + requirements: dependency.requirements, + updated_source: updated_source, + latest_version: latest_version&.to_s, + latest_resolvable_version: latest_resolvable_version&.to_s + ).updated_requirements end private @@ -150,15 +153,72 @@ def latest_git_tag_is_resolvable? @git_tag_resolvable = false end + def updated_source + # Never need to update source, unless a git_dependency + return dependency_source_details unless git_dependency? + + # Source becomes `nil` if switching to default rubygems + return default_source if should_switch_source_from_ref_to_release? + + # Update the git tag if updating a pinned version + if git_commit_checker.pinned_ref_looks_like_version? && + latest_git_tag_is_resolvable? + new_tag = git_commit_checker.local_tag_for_latest_version + return dependency_source_details.merge(ref: new_tag.fetch(:tag)) + end + + # Otherwise return the original source + dependency_source_details + end + + def dependency_source_details + sources = + dependency.requirements.map { |r| r.fetch(:source) }.uniq.compact + + raise "Multiple sources! #{sources.join(', ')}" if sources.count > 1 + + sources.first + end + + def should_switch_source_from_ref_to_release? + return false unless git_dependency? + return false if latest_resolvable_version_for_git_dependency.nil? + Gem::Version.correct?(latest_resolvable_version_for_git_dependency) + end + def git_dependency? git_commit_checker.git_dependency? end + def default_source + original_declaration = + parsed_file(manifest). + values_at(*FileParsers::Go::Dep::REQUIREMENT_TYPES). + flatten.compact. + find { |d| d["name"] == dependency.name } + + { + type: "default", + source: original_declaration["source"] || dependency.name + } + end + def git_branch_or_ref_in_release?(release) return false unless release git_commit_checker.branch_or_ref_in_release?(release) end + def parsed_file(file) + @parsed_file ||= {} + @parsed_file[file.name] ||= TomlRB.parse(file.content) + rescue TomlRB::ParseError + raise Dependabot::DependencyFileNotParseable, file.path + end + + def manifest + @manifest ||= dependency_files.find { |f| f.name == "Gopkg.toml" } + end + def git_commit_checker @git_commit_checker ||= GitCommitChecker.new( diff --git a/lib/dependabot/update_checkers/go/dep/requirements_updater.rb b/lib/dependabot/update_checkers/go/dep/requirements_updater.rb index 5b9f9741747..c2af5241126 100644 --- a/lib/dependabot/update_checkers/go/dep/requirements_updater.rb +++ b/lib/dependabot/update_checkers/go/dep/requirements_updater.rb @@ -13,10 +13,9 @@ class UnfixableRequirement < StandardError; end VERSION_REGEX = /[0-9]+(?:\.[A-Za-z0-9\-*]+)*/ - def initialize(requirements:, library:, updated_source:, + def initialize(requirements:, updated_source:, latest_version:, latest_resolvable_version:) @requirements = requirements - @library = library @updated_source = updated_source if latest_version && version_class.correct?(latest_version) @@ -46,12 +45,8 @@ def updated_requirements attr_reader :requirements, :updated_source, :latest_version, :latest_resolvable_version - def library? - @library - end - def updating_from_git_to_version? - return false unless updated_source.nil? + return false unless updated_source&.fetch(:type) == "default" original_source = requirements.map { |r| r[:source] }.compact.first original_source&.fetch(:type) == "git" end diff --git a/spec/dependabot/file_parsers/go/dep_spec.rb b/spec/dependabot/file_parsers/go/dep_spec.rb index 5303a75d070..eb3c8787759 100644 --- a/spec/dependabot/file_parsers/go/dep_spec.rb +++ b/spec/dependabot/file_parsers/go/dep_spec.rb @@ -67,15 +67,15 @@ context "that doesn't declare a version" do subject(:dependency) do - dependencies.find { |d| d.name == "golang.org/x/text" } + dependencies.find { |d| d.name == "github.com/dgrijalva/jwt-go" } end let(:manifest_fixture_name) { "no_version.toml" } let(:lockfile_fixture_name) { "no_version.lock" } it "has the right details" do expect(dependency).to be_a(Dependabot::Dependency) - expect(dependency.name).to eq("golang.org/x/text") - expect(dependency.version).to eq("0.2.0") + expect(dependency.name).to eq("github.com/dgrijalva/jwt-go") + expect(dependency.version).to eq("1.0.1") expect(dependency.requirements).to eq( [{ requirement: nil, @@ -83,7 +83,7 @@ groups: [], source: { type: "default", - source: "golang.org/x/text" + source: "github.com/dgrijalva/jwt-go" } }] ) diff --git a/spec/dependabot/update_checkers/go/dep/file_preparer_spec.rb b/spec/dependabot/update_checkers/go/dep/file_preparer_spec.rb index 90b28beed3f..300537301a8 100644 --- a/spec/dependabot/update_checkers/go/dep/file_preparer_spec.rb +++ b/spec/dependabot/update_checkers/go/dep/file_preparer_spec.rb @@ -52,10 +52,10 @@ source: source }] end - let(:dependency_name) { "golang.org/x/text" } - let(:dependency_version) { "0.2.0" } - let(:string_req) { "0.2.0" } - let(:source) { { type: "default", source: "golang.org/x/text" } } + let(:dependency_name) { "github.com/dgrijalva/jwt-go" } + let(:source) { { type: "default", source: "github.com/dgrijalva/jwt-go" } } + let(:dependency_version) { "1.0.1" } + let(:string_req) { "1.0.0" } describe "#prepared_dependency_files" do subject(:prepared_dependency_files) { preparer.prepared_dependency_files } @@ -71,7 +71,7 @@ let(:unlock_requirement) { false } it "doesn't update the requirement" do - expect(prepared_manifest_file.content).to include('version = "0.2.0"') + expect(prepared_manifest_file.content).to include('version = "1.0.0"') end end @@ -80,17 +80,17 @@ it "updates the requirement" do expect(prepared_manifest_file.content). - to include('version = ">= 0.2.0"') + to include('version = ">= 1.0.1"') end context "without a lockfile" do let(:dependency_files) { [manifest] } let(:dependency_version) { nil } - let(:string_req) { "0.2.0" } + let(:string_req) { "1.0.0" } it "updates the requirement" do expect(prepared_manifest_file.content). - to include('version = ">= 0.2.0"') + to include('version = ">= 1.0.0"') end end @@ -101,7 +101,7 @@ it "updates the requirement" do expect(prepared_manifest_file.content). - to include('version = ">= 0.2.0"') + to include('version = ">= 1.0.1"') end context "and a latest_allowable_version" do @@ -109,7 +109,7 @@ it "updates the requirement" do expect(prepared_manifest_file.content). - to include('version = ">= 0.2.0, <= 1.6.0"') + to include('version = ">= 1.0.1, <= 1.6.0"') end context "that is lower than the current lower bound" do @@ -117,7 +117,7 @@ it "updates the requirement" do expect(prepared_manifest_file.content). - to include('version = ">= 0.2.0"') + to include('version = ">= 1.0.1"') end end end @@ -138,6 +138,7 @@ context "with a branch" do let(:manifest_fixture_name) { "branch.toml" } let(:lockfile_fixture_name) { "branch.lock" } + let(:dependency_name) { "golang.org/x/text" } let(:dependency_version) do "7dd2c8130f5e924233f5543598300651c386d431" end @@ -172,6 +173,7 @@ let(:manifest_fixture_name) { "tag_as_revision.toml" } let(:lockfile_fixture_name) { "tag_as_revision.lock" } let(:dependency_version) { "v0.2.0" } + let(:dependency_name) { "golang.org/x/text" } let(:string_req) { nil } let(:source) do { diff --git a/spec/dependabot/update_checkers/go/dep/requirements_updater_spec.rb b/spec/dependabot/update_checkers/go/dep/requirements_updater_spec.rb index aa5568b43e1..bccff650d18 100644 --- a/spec/dependabot/update_checkers/go/dep/requirements_updater_spec.rb +++ b/spec/dependabot/update_checkers/go/dep/requirements_updater_spec.rb @@ -8,7 +8,6 @@ described_class.new( requirements: requirements, updated_source: updated_source, - library: library, latest_version: latest_version, latest_resolvable_version: latest_resolvable_version ) @@ -26,7 +25,6 @@ end let(:manifest_req_string) { "^1.4.0" } - let(:library) { false } let(:latest_version) { "1.8.0" } let(:latest_resolvable_version) { "1.5.0" } let(:version_class) { Dependabot::Utils::Go::Version } @@ -88,8 +86,13 @@ ) end - context "updating to use npm" do - let(:updated_source) { nil } + context "updating to use releases" do + let(:updated_source) do + { + type: "default", + source: "golang.org/x/text" + } + end it "updates the source and requirement" do expect(updater.updated_requirements). @@ -98,7 +101,10 @@ file: "Gopkg.toml", requirement: "^1.5.0", groups: [], - source: nil + source: { + type: "default", + source: "golang.org/x/text" + } }] ) end @@ -106,9 +112,7 @@ end end - context "for a library requirement" do - let(:library) { true } - + context "for a library-style update" do context "when there is a resolvable version" do let(:latest_resolvable_version) { Gem::Version.new("1.5.0") } diff --git a/spec/dependabot/update_checkers/go/dep/version_resolver_spec.rb b/spec/dependabot/update_checkers/go/dep/version_resolver_spec.rb index cac1ed6f310..364eddee5fb 100644 --- a/spec/dependabot/update_checkers/go/dep/version_resolver_spec.rb +++ b/spec/dependabot/update_checkers/go/dep/version_resolver_spec.rb @@ -54,10 +54,10 @@ let(:requirements) do [{ file: "Gopkg.toml", requirement: req_str, groups: [], source: source }] end - let(:dependency_name) { "golang.org/x/text" } - let(:dependency_version) { "0.2.0" } + let(:dependency_name) { "github.com/dgrijalva/jwt-go" } + let(:dependency_version) { "1.0.1" } let(:req_str) { nil } - let(:source) { { type: "default", source: "golang.org/x/text" } } + let(:source) { { type: "default", source: "github.com/dgrijalva/jwt-go" } } describe "latest_resolvable_version" do subject(:latest_resolvable_version) { resolver.latest_resolvable_version } @@ -68,7 +68,7 @@ context "that specifies a branch" do let(:manifest_fixture_name) { "branch.toml" } let(:lockfile_fixture_name) { "branch.lock" } - + let(:dependency_name) { "golang.org/x/text" } let(:source) do { type: "git", @@ -84,7 +84,7 @@ context "that specifies a tag as a revision" do let(:manifest_fixture_name) { "tag_as_revision.toml" } let(:lockfile_fixture_name) { "tag_as_revision.lock" } - + let(:dependency_name) { "golang.org/x/text" } let(:source) do { type: "git", diff --git a/spec/dependabot/update_checkers/go/dep_spec.rb b/spec/dependabot/update_checkers/go/dep_spec.rb index a54280adaab..e630e33bed2 100644 --- a/spec/dependabot/update_checkers/go/dep_spec.rb +++ b/spec/dependabot/update_checkers/go/dep_spec.rb @@ -49,26 +49,36 @@ let(:requirements) do [{ file: "Gopkg.toml", requirement: req_str, groups: [], source: source }] end - let(:dependency_name) { "golang.org/x/text" } - let(:dependency_version) { "0.2.0" } + let(:dependency_name) { "github.com/dgrijalva/jwt-go" } + let(:dependency_version) { "1.0.1" } let(:req_str) { nil } - let(:source) { { type: "default", source: "golang.org/x/text" } } + let(:source) { { type: "default", source: "github.com/dgrijalva/jwt-go" } } - let(:service_pack_url) do - "https://github.com/golang/text.git/info/refs"\ - "?service=git-upload-pack" - end before do - stub_request(:get, service_pack_url). + jwt_service_pack_url = + "https://github.com/dgrijalva/jwt-go.git/info/refs"\ + "?service=git-upload-pack" + stub_request(:get, jwt_service_pack_url). to_return( status: 200, - body: fixture("git", "upload_packs", upload_pack_fixture), + body: fixture("git", "upload_packs", "jwt-go"), + headers: { + "content-type" => "application/x-git-upload-pack-advertisement" + } + ) + + text_service_pack_url = + "https://github.com/golang/text.git/info/refs"\ + "?service=git-upload-pack" + stub_request(:get, text_service_pack_url). + to_return( + status: 200, + body: fixture("git", "upload_packs", "text"), headers: { "content-type" => "application/x-git-upload-pack-advertisement" } ) end - let(:upload_pack_fixture) { "text" } describe "#latest_version" do subject { checker.latest_version } @@ -81,7 +91,7 @@ ignored_versions: ignored_versions ).and_call_original - expect(checker.latest_version).to eq(Gem::Version.new("0.3.0")) + expect(checker.latest_version).to eq(Gem::Version.new("3.2.0")) end end @@ -94,7 +104,7 @@ dependency: dependency, unlock_requirement: true, remove_git_source: false, - latest_allowable_version: Gem::Version.new("0.3.0") + latest_allowable_version: Gem::Version.new("3.2.0") ).prepared_dependency_files expect(described_class::VersionResolver).to receive(:new).with( @@ -103,20 +113,21 @@ credentials: credentials ).and_call_original - expect(latest_resolvable_version).to eq(Gem::Version.new("0.3.0")) + expect(latest_resolvable_version).to eq(Gem::Version.new("3.2.0")) end context "with a manifest file that needs unlocking" do let(:manifest_fixture_name) { "bare_version.toml" } let(:lockfile_fixture_name) { "bare_version.lock" } - let(:req_str) { "0.2.0" } + let(:req_str) { "1.0.0" } it "unlocks the manifest and gets the correct version" do - expect(latest_resolvable_version).to eq(Gem::Version.new("0.3.0")) + expect(latest_resolvable_version).to eq(Gem::Version.new("3.2.0")) end end context "with a git dependency" do + let(:dependency_name) { "golang.org/x/text" } let(:source) do { type: "git", @@ -193,7 +204,7 @@ dependency: dependency, unlock_requirement: true, remove_git_source: false, - latest_allowable_version: Gem::Version.new("0.3.0") + latest_allowable_version: Gem::Version.new("3.2.0") ).prepared_dependency_files expect(described_class::VersionResolver).to receive(:new).with( @@ -203,17 +214,17 @@ ).and_call_original expect(checker.latest_resolvable_version_with_no_unlock). - to eq(Gem::Version.new("0.3.0")) + to eq(Gem::Version.new("3.2.0")) end context "with a manifest file that needs unlocking" do let(:manifest_fixture_name) { "bare_version.toml" } let(:lockfile_fixture_name) { "bare_version.lock" } - let(:req_str) { "0.2.0" } + let(:req_str) { "1.0.0" } it "doesn't unlock the manifest" do expect(checker.latest_resolvable_version_with_no_unlock). - to eq(Gem::Version.new("0.2.0")) + to eq(Gem::Version.new("1.0.2")) end end @@ -226,6 +237,7 @@ ref: ref } end + let(:dependency_name) { "golang.org/x/text" } before do repo_url = "https://api.github.com/repos/golang/text" @@ -269,4 +281,84 @@ end end end + + describe "#updated_requirements" do + subject { checker.updated_requirements } + + it "delegates to RequirementsUpdater" do + expect(described_class::RequirementsUpdater).to receive(:new).with( + requirements: dependency.requirements, + updated_source: source, + latest_version: "3.2.0", + latest_resolvable_version: "3.2.0" + ).and_call_original + + expect(checker.updated_requirements).to eq( + [{ + file: "Gopkg.toml", + requirement: nil, + groups: [], + source: { type: "default", source: "github.com/dgrijalva/jwt-go" } + }] + ) + end + + context "with a manifest file that needs unlocking" do + let(:manifest_fixture_name) { "bare_version.toml" } + let(:lockfile_fixture_name) { "bare_version.lock" } + let(:req_str) { "1.0.0" } + + it "updates the requirements for the new version range" do + expect(checker.updated_requirements).to eq( + [{ + file: "Gopkg.toml", + requirement: ">= 1.0.0, < 4.0.0", + groups: [], + source: { type: "default", source: "github.com/dgrijalva/jwt-go" } + }] + ) + end + end + + context "with a git dependency we should switch" do + let(:manifest_fixture_name) { "tag_as_revision.toml" } + let(:lockfile_fixture_name) { "tag_as_revision.lock" } + + let(:dependency_name) { "golang.org/x/text" } + let(:req_str) { nil } + let(:dependency_version) { "v0.2.0" } + let(:source) do + { + type: "git", + url: "https://github.com/golang/text", + branch: nil, + ref: "v0.2.0" + } + end + + before do + repo_url = "https://api.github.com/repos/golang/text" + stub_request(:get, repo_url + "/compare/v0.3.0...v0.2.0"). + to_return( + status: 200, + body: commit_compare_response, + headers: { "Content-Type" => "application/json" } + ) + end + let(:commit_compare_response) do + fixture("github", "commit_compare_behind.json") + end + + it "updates the requirements for the new version range" do + expect(checker.updated_requirements).to eq( + [{ + file: "Gopkg.toml", + requirement: "^0.3.0", + groups: [], + source: { type: "default", source: "golang.org/x/text" } + }] + ) + end + end + end end diff --git a/spec/fixtures/git/upload_packs/jwt-go b/spec/fixtures/git/upload_packs/jwt-go new file mode 100644 index 0000000000000000000000000000000000000000..f46f4edb830652d4b511ea68220fe8f1d84e6eac GIT binary patch literal 12105 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 Date: Wed, 25 Jul 2018 10:40:02 +0100 Subject: [PATCH 25/28] Dep: First version of FileUpdater (Gopkg.toml only) --- lib/dependabot/file_updaters/go/dep.rb | 114 ++++++++++++++++++- spec/dependabot/file_updaters/go/dep_spec.rb | 105 +++++++++++++++++ 2 files changed, 213 insertions(+), 6 deletions(-) diff --git a/lib/dependabot/file_updaters/go/dep.rb b/lib/dependabot/file_updaters/go/dep.rb index b2b08cafc98..5068b31a49c 100644 --- a/lib/dependabot/file_updaters/go/dep.rb +++ b/lib/dependabot/file_updaters/go/dep.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +require "dependabot/shared_helpers" require "dependabot/file_updaters/base" module Dependabot @@ -24,8 +25,12 @@ def updated_dependency_files ) end - updated_files << - updated_file(file: lockfile, content: updated_lockfile_content) + if lockfile + updated_files << + updated_file(file: lockfile, content: updated_lockfile_content) + end + + raise "No files changed!" if updated_files.none? updated_files end @@ -33,9 +38,7 @@ def updated_dependency_files private def check_required_files - %w(Gopkg.toml Gopkg.lock).each do |filename| - raise "No #{filename}!" unless get_original_file(filename) - end + raise "No Gopkg.toml!" unless get_original_file("Gopkg.toml") end def manifest @@ -47,12 +50,111 @@ def lockfile end def updated_manifest_content - # TODO: This can normally be written using regexs + dependencies. + select { |dep| requirement_changed?(manifest, dep) }. + reduce(manifest.content.dup) do |content, dep| + updated_content = content + updated_content = update_requirements( + content: updated_content, + filename: manifest.name, + dependency: dep + ) + updated_content = update_git_pin( + content: updated_content, + filename: manifest.name, + dependency: dep + ) + + raise "Expected content to change!" if content == updated_content + updated_content + end end def updated_lockfile_content # TODO: This normally needs to be written in the native language. # We do so by shelling out to a helper method (see other languages) + lockfile.content + end + + def update_requirements(content:, filename:, dependency:) + updated_content = content.dup + + # The UpdateChecker ensures the order of requirements is preserved + # when updating, so we can zip them together in new/old pairs. + reqs = dependency.requirements.zip(dependency.previous_requirements). + reject { |new_req, old_req| new_req == old_req } + + # Loop through each changed requirement + reqs.each do |new_req, old_req| + raise "Bad req match" unless new_req[:file] == old_req[:file] + next if new_req[:requirement] == old_req[:requirement] + next unless new_req[:file] == filename + + updated_content = update_manifest_req( + content: updated_content, + dep: dependency, + old_req: old_req.fetch(:requirement), + new_req: new_req.fetch(:requirement) + ) + end + + updated_content + end + + def update_git_pin(content:, filename:, dependency:) + updated_pin = + dependency.requirements. + find { |r| r[:file] == filename }&. + dig(:source, :ref) + + old_pin = + dependency.previous_requirements. + find { |r| r[:file] == filename }&. + dig(:source, :ref) + + return content unless old_pin + + update_manifest_pin( + content: content, + dep: dependency, + old_pin: old_pin, + new_pin: updated_pin + ) + end + + def update_manifest_req(content:, dep:, old_req:, new_req:) + simple_declaration = content.scan(declaration_regex(dep)). + find { |m| m.include?(old_req) } + + if simple_declaration + content.gsub(simple_declaration) do |line| + line.gsub(old_req, new_req) + end + else + content + end + end + + def update_manifest_pin(content:, dep:, old_pin:, new_pin:) + simple_declaration = content.scan(declaration_regex(dep)). + find { |m| m.include?(old_pin) } + + if simple_declaration + content.gsub(simple_declaration) do |line| + line.gsub(old_pin, new_pin) + end + else + content + end + end + + def declaration_regex(dep) + / + (?<=\]\]) + (?:(?!^\[).)* + name\s*=\s*["']#{Regexp.escape(dep.name)}["'] + (?:(?!^\[).)* + /mx end end end diff --git a/spec/dependabot/file_updaters/go/dep_spec.rb b/spec/dependabot/file_updaters/go/dep_spec.rb index 2cbbdeb595a..01f7b3a43e6 100644 --- a/spec/dependabot/file_updaters/go/dep_spec.rb +++ b/spec/dependabot/file_updaters/go/dep_spec.rb @@ -1,8 +1,113 @@ # frozen_string_literal: true +require "dependabot/dependency" +require "dependabot/dependency_file" require "dependabot/file_updaters/go/dep" require_relative "../shared_examples_for_file_updaters" RSpec.describe Dependabot::FileUpdaters::Go::Dep do it_behaves_like "a dependency file updater" + + let(:updater) do + described_class.new( + dependency_files: files, + dependencies: [dependency], + credentials: [{ + "type" => "git_source", + "host" => "github.com", + "username" => "x-access-token", + "password" => "token" + }] + ) + end + + let(:files) { [manifest, lockfile] } + let(:manifest) do + Dependabot::DependencyFile.new(name: "Gopkg.toml", content: manifest_body) + end + let(:lockfile) do + Dependabot::DependencyFile.new(name: "Gopkg.lock", content: lockfile_body) + end + let(:manifest_body) { fixture("go", "gopkg_tomls", manifest_fixture_name) } + let(:lockfile_body) { fixture("go", "gopkg_locks", lockfile_fixture_name) } + let(:manifest_fixture_name) { "bare_version.toml" } + let(:lockfile_fixture_name) { "bare_version.lock" } + + let(:dependency) do + Dependabot::Dependency.new( + name: dependency_name, + version: dependency_version, + requirements: requirements, + previous_version: dependency_previous_version, + previous_requirements: previous_requirements, + package_manager: "dep" + ) + end + let(:dependency_name) { "github.com/dgrijalva/jwt-go" } + let(:dependency_version) { "3.2.0" } + let(:dependency_previous_version) { "1.0.1" } + let(:requirements) { previous_requirements } + let(:previous_requirements) do + [{ + file: "Gopkg.toml", + requirement: "1.0.0", + groups: [], + source: { + type: "default", + source: "github.com/dgrijalva/jwt-go" + } + }] + end + let(:tmp_path) { Dependabot::SharedHelpers::BUMP_TMP_DIR_PATH } + + before { Dir.mkdir(tmp_path) unless Dir.exist?(tmp_path) } + + describe "#updated_dependency_files" do + subject(:updated_files) { updater.updated_dependency_files } + + it "doesn't store the files permanently, and returns DependencyFiles" do + expect { updated_files }.to_not(change { Dir.entries(tmp_path) }) + updated_files.each { |f| expect(f).to be_a(Dependabot::DependencyFile) } + end + + it { expect { updated_files }.to_not output.to_stdout } + + context "without a lockfile" do + let(:files) { [manifest] } + + context "if no files have changed" do + it "raises a helpful error" do + expect { updater.updated_dependency_files }. + to raise_error("No files changed!") + end + end + + context "when the manifest has changed" do + let(:requirements) do + [{ + file: "Gopkg.toml", + requirement: ">= 1.0.0, < 4.0.0", + groups: [], + source: { + type: "default", + source: "github.com/dgrijalva/jwt-go" + } + }] + end + + its(:length) { is_expected.to eq(1) } + + describe "the updated manifest" do + subject(:updated_manifest_content) do + updated_files.find { |f| f.name == "Gopkg.toml" }.content + end + + it "includes the new requirement" do + expect(updated_manifest_content). + to include(%(version = ">= 1.0.0, < 4.0.0")) + end + end + end + end + end end From f46c1c26386351d334e77032a7285b115bfe00ab Mon Sep 17 00:00:00 2001 From: Grey Baker Date: Wed, 25 Jul 2018 11:15:59 +0100 Subject: [PATCH 26/28] Dep: Handle switch from git revision to release in FileUpdater --- lib/dependabot/file_updaters/go/dep.rb | 42 +++-- spec/dependabot/file_updaters/go/dep_spec.rb | 162 ++++++++++++++++++- 2 files changed, 191 insertions(+), 13 deletions(-) diff --git a/lib/dependabot/file_updaters/go/dep.rb b/lib/dependabot/file_updaters/go/dep.rb index 5068b31a49c..497444684fd 100644 --- a/lib/dependabot/file_updaters/go/dep.rb +++ b/lib/dependabot/file_updaters/go/dep.rb @@ -122,29 +122,47 @@ def update_git_pin(content:, filename:, dependency:) ) end + # rubocop:disable Metrics/CyclomaticComplexity + # rubocop:disable Metrics/PerceivedComplexity def update_manifest_req(content:, dep:, old_req:, new_req:) - simple_declaration = content.scan(declaration_regex(dep)). - find { |m| m.include?(old_req) } + declaration = content.scan(declaration_regex(dep)). + find { |m| old_req.nil? || m.include?(old_req) } - if simple_declaration - content.gsub(simple_declaration) do |line| + return content unless declaration + + if old_req && new_req + content.gsub(declaration) do |line| line.gsub(old_req, new_req) end - else - content + elsif old_req && new_req.nil? + content.gsub(declaration) do |line| + line.gsub(/\R+.*version\s*=.*/, "") + end + elsif old_req.nil? && new_req + content.gsub(declaration) do |line| + indent = line.match(/(?\s*)name/).named_captures["indent"] + version_declaration = indent + "version = \"#{new_req}\"" + line.gsub(/name\s*=.*/) { |nm_ln| nm_ln + version_declaration } + end end end + # rubocop:enable Metrics/CyclomaticComplexity + # rubocop:enable Metrics/PerceivedComplexity def update_manifest_pin(content:, dep:, old_pin:, new_pin:) - simple_declaration = content.scan(declaration_regex(dep)). - find { |m| m.include?(old_pin) } + declaration = content.scan(declaration_regex(dep)). + find { |m| m.include?(old_pin) } - if simple_declaration - content.gsub(simple_declaration) do |line| + return content unless declaration + + if old_pin && new_pin + content.gsub(declaration) do |line| line.gsub(old_pin, new_pin) end - else - content + elsif old_pin && new_pin.nil? + content.gsub(declaration) do |line| + line.gsub(/\R+.*revision\s*=.*/, "").gsub(/\R+.*branch\s*=.*/, "") + end end end diff --git a/spec/dependabot/file_updaters/go/dep_spec.rb b/spec/dependabot/file_updaters/go/dep_spec.rb index 01f7b3a43e6..9e193b1c970 100644 --- a/spec/dependabot/file_updaters/go/dep_spec.rb +++ b/spec/dependabot/file_updaters/go/dep_spec.rb @@ -82,7 +82,7 @@ end end - context "when the manifest has changed" do + context "when the requirement in the manifest has changed" do let(:requirements) do [{ file: "Gopkg.toml", @@ -108,6 +108,166 @@ end end end + + context "when the requirement in the manifest has been deleted" do + let(:requirements) do + [{ + file: "Gopkg.toml", + requirement: nil, + groups: [], + source: { + type: "default", + source: "github.com/dgrijalva/jwt-go" + } + }] + end + + its(:length) { is_expected.to eq(1) } + + describe "the updated manifest" do + subject(:updated_manifest_content) do + updated_files.find { |f| f.name == "Gopkg.toml" }.content + end + + it "includes the new requirement" do + expect(updated_manifest_content). + to end_with(%(name = "github.com/dgrijalva/jwt-go"\n)) + end + end + end + + context "when a requirement is being added to the manifest" do + let(:manifest_fixture_name) { "no_version.toml" } + let(:lockfile_fixture_name) { "no_version.lock" } + let(:previous_requirements) do + [{ + file: "Gopkg.toml", + requirement: nil, + groups: [], + source: { + type: "default", + source: "github.com/dgrijalva/jwt-go" + } + }] + end + let(:requirements) do + [{ + file: "Gopkg.toml", + requirement: ">= 1.0.0, < 4.0.0", + groups: [], + source: { + type: "default", + source: "github.com/dgrijalva/jwt-go" + } + }] + end + + its(:length) { is_expected.to eq(1) } + + describe "the updated manifest" do + subject(:updated_manifest_content) do + updated_files.find { |f| f.name == "Gopkg.toml" }.content + end + + it "includes the new requirement" do + expect(updated_manifest_content). + to end_with(" name = \"github.com/dgrijalva/jwt-go\"\n"\ + " version = \">= 1.0.0, < 4.0.0\"\n") + end + end + end + + context "when the tag in the manifest has changed" do + let(:manifest_fixture_name) { "tag_as_revision.toml" } + let(:lockfile_fixture_name) { "tag_as_revision.lock" } + let(:dependency_name) { "golang.org/x/text" } + let(:dependency_version) { "v0.3.0" } + let(:dependency_previous_version) { "v0.2.0" } + let(:requirements) do + [{ + requirement: nil, + file: "Gopkg.toml", + groups: [], + source: { + type: "git", + url: "https://github.com/golang/text", + branch: nil, + ref: "v0.3.0" + } + }] + end + let(:previous_requirements) do + [{ + requirement: nil, + file: "Gopkg.toml", + groups: [], + source: { + type: "git", + url: "https://github.com/golang/text", + branch: nil, + ref: "v0.2.0" + } + }] + end + + its(:length) { is_expected.to eq(1) } + + describe "the updated manifest" do + subject(:updated_manifest_content) do + updated_files.find { |f| f.name == "Gopkg.toml" }.content + end + + it "includes the new tag" do + expect(updated_manifest_content).to include(%(revision = "v0.3.0")) + end + end + end + + context "when switching from a git revision to a release" do + let(:manifest_fixture_name) { "tag_as_revision.toml" } + let(:lockfile_fixture_name) { "tag_as_revision.lock" } + let(:dependency_name) { "golang.org/x/text" } + let(:dependency_version) { "0.3.0" } + let(:dependency_previous_version) { "v0.2.0" } + let(:requirements) do + [{ + requirement: "^0.3.0", + file: "Gopkg.toml", + groups: [], + source: { + type: "default", + source: "golang.org/x/text" + } + }] + end + let(:previous_requirements) do + [{ + requirement: nil, + file: "Gopkg.toml", + groups: [], + source: { + type: "git", + url: "https://github.com/golang/text", + branch: nil, + ref: "v0.2.0" + } + }] + end + + its(:length) { is_expected.to eq(1) } + + describe "the updated manifest" do + subject(:updated_manifest_content) do + updated_files.find { |f| f.name == "Gopkg.toml" }.content + end + + it "includes the new tag" do + expect(updated_manifest_content). + to end_with(" name = \"golang.org/x/text\"\n"\ + " version = \"^0.3.0\"\n") + end + end + end end end end From 8fb18c6e92a4cd7deb95e9dcb38322a18c7e9d0c Mon Sep 17 00:00:00 2001 From: Grey Baker Date: Wed, 25 Jul 2018 13:17:59 +0100 Subject: [PATCH 27/28] Dep: Move manifest updating logic into separate class --- lib/dependabot/file_updaters/go/dep.rb | 123 +---------- .../file_updaters/go/dep/manifest_updater.rb | 156 ++++++++++++++ .../go/dep/manifest_updater_spec.rb | 192 ++++++++++++++++++ spec/dependabot/file_updaters/go/dep_spec.rb | 165 +-------------- 4 files changed, 359 insertions(+), 277 deletions(-) create mode 100644 lib/dependabot/file_updaters/go/dep/manifest_updater.rb create mode 100644 spec/dependabot/file_updaters/go/dep/manifest_updater_spec.rb diff --git a/lib/dependabot/file_updaters/go/dep.rb b/lib/dependabot/file_updaters/go/dep.rb index 497444684fd..eb5ed471d0f 100644 --- a/lib/dependabot/file_updaters/go/dep.rb +++ b/lib/dependabot/file_updaters/go/dep.rb @@ -7,6 +7,8 @@ module Dependabot module FileUpdaters module Go class Dep < Dependabot::FileUpdaters::Base + require_relative "dep/manifest_updater" + def self.updated_files_regex [ /^Gopkg\.toml$/, @@ -50,24 +52,10 @@ def lockfile end def updated_manifest_content - dependencies. - select { |dep| requirement_changed?(manifest, dep) }. - reduce(manifest.content.dup) do |content, dep| - updated_content = content - updated_content = update_requirements( - content: updated_content, - filename: manifest.name, - dependency: dep - ) - updated_content = update_git_pin( - content: updated_content, - filename: manifest.name, - dependency: dep - ) - - raise "Expected content to change!" if content == updated_content - updated_content - end + ManifestUpdater.new( + dependencies: dependencies, + manifest: manifest + ).updated_manifest_content end def updated_lockfile_content @@ -75,105 +63,6 @@ def updated_lockfile_content # We do so by shelling out to a helper method (see other languages) lockfile.content end - - def update_requirements(content:, filename:, dependency:) - updated_content = content.dup - - # The UpdateChecker ensures the order of requirements is preserved - # when updating, so we can zip them together in new/old pairs. - reqs = dependency.requirements.zip(dependency.previous_requirements). - reject { |new_req, old_req| new_req == old_req } - - # Loop through each changed requirement - reqs.each do |new_req, old_req| - raise "Bad req match" unless new_req[:file] == old_req[:file] - next if new_req[:requirement] == old_req[:requirement] - next unless new_req[:file] == filename - - updated_content = update_manifest_req( - content: updated_content, - dep: dependency, - old_req: old_req.fetch(:requirement), - new_req: new_req.fetch(:requirement) - ) - end - - updated_content - end - - def update_git_pin(content:, filename:, dependency:) - updated_pin = - dependency.requirements. - find { |r| r[:file] == filename }&. - dig(:source, :ref) - - old_pin = - dependency.previous_requirements. - find { |r| r[:file] == filename }&. - dig(:source, :ref) - - return content unless old_pin - - update_manifest_pin( - content: content, - dep: dependency, - old_pin: old_pin, - new_pin: updated_pin - ) - end - - # rubocop:disable Metrics/CyclomaticComplexity - # rubocop:disable Metrics/PerceivedComplexity - def update_manifest_req(content:, dep:, old_req:, new_req:) - declaration = content.scan(declaration_regex(dep)). - find { |m| old_req.nil? || m.include?(old_req) } - - return content unless declaration - - if old_req && new_req - content.gsub(declaration) do |line| - line.gsub(old_req, new_req) - end - elsif old_req && new_req.nil? - content.gsub(declaration) do |line| - line.gsub(/\R+.*version\s*=.*/, "") - end - elsif old_req.nil? && new_req - content.gsub(declaration) do |line| - indent = line.match(/(?\s*)name/).named_captures["indent"] - version_declaration = indent + "version = \"#{new_req}\"" - line.gsub(/name\s*=.*/) { |nm_ln| nm_ln + version_declaration } - end - end - end - # rubocop:enable Metrics/CyclomaticComplexity - # rubocop:enable Metrics/PerceivedComplexity - - def update_manifest_pin(content:, dep:, old_pin:, new_pin:) - declaration = content.scan(declaration_regex(dep)). - find { |m| m.include?(old_pin) } - - return content unless declaration - - if old_pin && new_pin - content.gsub(declaration) do |line| - line.gsub(old_pin, new_pin) - end - elsif old_pin && new_pin.nil? - content.gsub(declaration) do |line| - line.gsub(/\R+.*revision\s*=.*/, "").gsub(/\R+.*branch\s*=.*/, "") - end - end - end - - def declaration_regex(dep) - / - (?<=\]\]) - (?:(?!^\[).)* - name\s*=\s*["']#{Regexp.escape(dep.name)}["'] - (?:(?!^\[).)* - /mx - end end end end diff --git a/lib/dependabot/file_updaters/go/dep/manifest_updater.rb b/lib/dependabot/file_updaters/go/dep/manifest_updater.rb new file mode 100644 index 00000000000..6503e237c21 --- /dev/null +++ b/lib/dependabot/file_updaters/go/dep/manifest_updater.rb @@ -0,0 +1,156 @@ +# frozen_string_literal: true + +require "parser/current" +require "dependabot/file_updaters/go/dep" + +module Dependabot + module FileUpdaters + module Go + class Dep + class ManifestUpdater + def initialize(dependencies:, manifest:) + @dependencies = dependencies + @manifest = manifest + end + + def updated_manifest_content + dependencies. + select { |dep| requirement_changed?(manifest, dep) }. + reduce(manifest.content.dup) do |content, dep| + updated_content = content + + updated_content = update_requirements( + content: updated_content, + filename: manifest.name, + dependency: dep + ) + updated_content = update_git_pin( + content: updated_content, + filename: manifest.name, + dependency: dep + ) + + if content == updated_content + raise "Expected content to change!" + end + + updated_content + end + end + + private + + attr_reader :dependencies, :manifest + + def requirement_changed?(file, dependency) + changed_requirements = + dependency.requirements - dependency.previous_requirements + + changed_requirements.any? { |f| f[:file] == file.name } + end + + def update_requirements(content:, filename:, dependency:) + updated_content = content.dup + + # The UpdateChecker ensures the order of requirements is preserved + # when updating, so we can zip them together in new/old pairs. + reqs = dependency.requirements. + zip(dependency.previous_requirements). + reject { |new_req, old_req| new_req == old_req } + + # Loop through each changed requirement + reqs.each do |new_req, old_req| + raise "Bad req match" unless new_req[:file] == old_req[:file] + next if new_req[:requirement] == old_req[:requirement] + next unless new_req[:file] == filename + + updated_content = update_manifest_req( + content: updated_content, + dep: dependency, + old_req: old_req.fetch(:requirement), + new_req: new_req.fetch(:requirement) + ) + end + + updated_content + end + + def update_git_pin(content:, filename:, dependency:) + updated_pin = + dependency.requirements. + find { |r| r[:file] == filename }&. + dig(:source, :ref) + + old_pin = + dependency.previous_requirements. + find { |r| r[:file] == filename }&. + dig(:source, :ref) + + return content unless old_pin + + update_manifest_pin( + content: content, + dep: dependency, + old_pin: old_pin, + new_pin: updated_pin + ) + end + + # rubocop:disable Metrics/CyclomaticComplexity + # rubocop:disable Metrics/PerceivedComplexity + def update_manifest_req(content:, dep:, old_req:, new_req:) + declaration = content.scan(declaration_regex(dep)). + find { |m| old_req.nil? || m.include?(old_req) } + + return content unless declaration + + if old_req && new_req + content.gsub(declaration) do |line| + line.gsub(old_req, new_req) + end + elsif old_req && new_req.nil? + content.gsub(declaration) do |line| + line.gsub(/\R+.*version\s*=.*/, "") + end + elsif old_req.nil? && new_req + content.gsub(declaration) do |line| + indent = line.match(/(?\s*)name/). + named_captures.fetch("indent") + version_declaration = indent + "version = \"#{new_req}\"" + line.gsub(/name\s*=.*/) { |nm_ln| nm_ln + version_declaration } + end + end + end + # rubocop:enable Metrics/CyclomaticComplexity + # rubocop:enable Metrics/PerceivedComplexity + + def update_manifest_pin(content:, dep:, old_pin:, new_pin:) + declaration = content.scan(declaration_regex(dep)). + find { |m| m.include?(old_pin) } + + return content unless declaration + + if old_pin && new_pin + content.gsub(declaration) do |line| + line.gsub(old_pin, new_pin) + end + elsif old_pin && new_pin.nil? + content.gsub(declaration) do |line| + line.gsub(/\R+.*(revision|branch)\s*=.*/, "") + end + end + end + + def declaration_regex(dep) + / + (?<=\]\]) + (?:(?!^\[).)* + name\s*=\s*["']#{Regexp.escape(dep.name)}["'] + (?:(?!^\[).)* + /mx + end + end + end + end + end +end diff --git a/spec/dependabot/file_updaters/go/dep/manifest_updater_spec.rb b/spec/dependabot/file_updaters/go/dep/manifest_updater_spec.rb new file mode 100644 index 00000000000..24393f2f214 --- /dev/null +++ b/spec/dependabot/file_updaters/go/dep/manifest_updater_spec.rb @@ -0,0 +1,192 @@ +# frozen_string_literal: true + +require "dependabot/dependency" +require "dependabot/dependency_file" +require "dependabot/file_updaters/go/dep/manifest_updater" + +RSpec.describe Dependabot::FileUpdaters::Go::Dep::ManifestUpdater do + let(:updater) do + described_class.new( + manifest: manifest, + dependencies: [dependency] + ) + end + + let(:manifest) do + Dependabot::DependencyFile.new(name: "Gopkg.toml", content: manifest_body) + end + let(:manifest_body) { fixture("go", "gopkg_tomls", manifest_fixture_name) } + let(:manifest_fixture_name) { "bare_version.toml" } + + let(:dependency) do + Dependabot::Dependency.new( + name: dependency_name, + version: dependency_version, + requirements: requirements, + previous_version: dependency_previous_version, + previous_requirements: previous_requirements, + package_manager: "dep" + ) + end + let(:dependency_name) { "github.com/dgrijalva/jwt-go" } + let(:dependency_version) { "3.2.0" } + let(:dependency_previous_version) { "1.0.1" } + let(:requirements) { previous_requirements } + let(:previous_requirements) do + [{ + file: "Gopkg.toml", + requirement: "1.0.0", + groups: [], + source: { + type: "default", + source: "github.com/dgrijalva/jwt-go" + } + }] + end + + describe "#updated_manifest_content" do + subject(:updated_manifest_content) { updater.updated_manifest_content } + + context "if no files have changed" do + it { is_expected.to eq(manifest.content) } + end + + context "when the requirement has changed" do + let(:requirements) do + [{ + file: "Gopkg.toml", + requirement: ">= 1.0.0, < 4.0.0", + groups: [], + source: { + type: "default", + source: "github.com/dgrijalva/jwt-go" + } + }] + end + + it { is_expected.to include(%(version = ">= 1.0.0, < 4.0.0")) } + end + + context "when the requirement in the manifest has been deleted" do + let(:requirements) do + [{ + file: "Gopkg.toml", + requirement: nil, + groups: [], + source: { + type: "default", + source: "github.com/dgrijalva/jwt-go" + } + }] + end + + it { is_expected.to end_with(%(name = "github.com/dgrijalva/jwt-go"\n)) } + end + + context "when a requirement is being added" do + let(:manifest_fixture_name) { "no_version.toml" } + let(:previous_requirements) do + [{ + file: "Gopkg.toml", + requirement: nil, + groups: [], + source: { + type: "default", + source: "github.com/dgrijalva/jwt-go" + } + }] + end + let(:requirements) do + [{ + file: "Gopkg.toml", + requirement: ">= 1.0.0, < 4.0.0", + groups: [], + source: { + type: "default", + source: "github.com/dgrijalva/jwt-go" + } + }] + end + + it "includes the new requirement" do + expect(updated_manifest_content). + to end_with(" name = \"github.com/dgrijalva/jwt-go\"\n"\ + " version = \">= 1.0.0, < 4.0.0\"\n") + end + end + + context "when the tag in the manifest has changed" do + let(:manifest_fixture_name) { "tag_as_revision.toml" } + let(:dependency_name) { "golang.org/x/text" } + let(:dependency_version) { "v0.3.0" } + let(:dependency_previous_version) { "v0.2.0" } + let(:requirements) do + [{ + requirement: nil, + file: "Gopkg.toml", + groups: [], + source: { + type: "git", + url: "https://github.com/golang/text", + branch: nil, + ref: "v0.3.0" + } + }] + end + let(:previous_requirements) do + [{ + requirement: nil, + file: "Gopkg.toml", + groups: [], + source: { + type: "git", + url: "https://github.com/golang/text", + branch: nil, + ref: "v0.2.0" + } + }] + end + + it "includes the new tag" do + expect(updated_manifest_content).to include(%(revision = "v0.3.0")) + end + end + + context "when switching from a git revision to a release" do + let(:manifest_fixture_name) { "tag_as_revision.toml" } + let(:dependency_name) { "golang.org/x/text" } + let(:dependency_version) { "0.3.0" } + let(:dependency_previous_version) { "v0.2.0" } + let(:requirements) do + [{ + requirement: "^0.3.0", + file: "Gopkg.toml", + groups: [], + source: { + type: "default", + source: "golang.org/x/text" + } + }] + end + let(:previous_requirements) do + [{ + requirement: nil, + file: "Gopkg.toml", + groups: [], + source: { + type: "git", + url: "https://github.com/golang/text", + branch: nil, + ref: "v0.2.0" + } + }] + end + + it "includes the new tag" do + expect(updated_manifest_content). + to end_with(" name = \"golang.org/x/text\"\n"\ + " version = \"^0.3.0\"\n") + end + end + end +end diff --git a/spec/dependabot/file_updaters/go/dep_spec.rb b/spec/dependabot/file_updaters/go/dep_spec.rb index 9e193b1c970..f1d0a34b5a6 100644 --- a/spec/dependabot/file_updaters/go/dep_spec.rb +++ b/spec/dependabot/file_updaters/go/dep_spec.rb @@ -103,168 +103,13 @@ end it "includes the new requirement" do - expect(updated_manifest_content). - to include(%(version = ">= 1.0.0, < 4.0.0")) - end - end - end - - context "when the requirement in the manifest has been deleted" do - let(:requirements) do - [{ - file: "Gopkg.toml", - requirement: nil, - groups: [], - source: { - type: "default", - source: "github.com/dgrijalva/jwt-go" - } - }] - end - - its(:length) { is_expected.to eq(1) } - - describe "the updated manifest" do - subject(:updated_manifest_content) do - updated_files.find { |f| f.name == "Gopkg.toml" }.content - end - - it "includes the new requirement" do - expect(updated_manifest_content). - to end_with(%(name = "github.com/dgrijalva/jwt-go"\n)) - end - end - end + expect(described_class::ManifestUpdater). + to receive(:new). + with(dependencies: [dependency], manifest: manifest). + and_call_original - context "when a requirement is being added to the manifest" do - let(:manifest_fixture_name) { "no_version.toml" } - let(:lockfile_fixture_name) { "no_version.lock" } - let(:previous_requirements) do - [{ - file: "Gopkg.toml", - requirement: nil, - groups: [], - source: { - type: "default", - source: "github.com/dgrijalva/jwt-go" - } - }] - end - let(:requirements) do - [{ - file: "Gopkg.toml", - requirement: ">= 1.0.0, < 4.0.0", - groups: [], - source: { - type: "default", - source: "github.com/dgrijalva/jwt-go" - } - }] - end - - its(:length) { is_expected.to eq(1) } - - describe "the updated manifest" do - subject(:updated_manifest_content) do - updated_files.find { |f| f.name == "Gopkg.toml" }.content - end - - it "includes the new requirement" do expect(updated_manifest_content). - to end_with(" name = \"github.com/dgrijalva/jwt-go\"\n"\ - " version = \">= 1.0.0, < 4.0.0\"\n") - end - end - end - - context "when the tag in the manifest has changed" do - let(:manifest_fixture_name) { "tag_as_revision.toml" } - let(:lockfile_fixture_name) { "tag_as_revision.lock" } - let(:dependency_name) { "golang.org/x/text" } - let(:dependency_version) { "v0.3.0" } - let(:dependency_previous_version) { "v0.2.0" } - let(:requirements) do - [{ - requirement: nil, - file: "Gopkg.toml", - groups: [], - source: { - type: "git", - url: "https://github.com/golang/text", - branch: nil, - ref: "v0.3.0" - } - }] - end - let(:previous_requirements) do - [{ - requirement: nil, - file: "Gopkg.toml", - groups: [], - source: { - type: "git", - url: "https://github.com/golang/text", - branch: nil, - ref: "v0.2.0" - } - }] - end - - its(:length) { is_expected.to eq(1) } - - describe "the updated manifest" do - subject(:updated_manifest_content) do - updated_files.find { |f| f.name == "Gopkg.toml" }.content - end - - it "includes the new tag" do - expect(updated_manifest_content).to include(%(revision = "v0.3.0")) - end - end - end - - context "when switching from a git revision to a release" do - let(:manifest_fixture_name) { "tag_as_revision.toml" } - let(:lockfile_fixture_name) { "tag_as_revision.lock" } - let(:dependency_name) { "golang.org/x/text" } - let(:dependency_version) { "0.3.0" } - let(:dependency_previous_version) { "v0.2.0" } - let(:requirements) do - [{ - requirement: "^0.3.0", - file: "Gopkg.toml", - groups: [], - source: { - type: "default", - source: "golang.org/x/text" - } - }] - end - let(:previous_requirements) do - [{ - requirement: nil, - file: "Gopkg.toml", - groups: [], - source: { - type: "git", - url: "https://github.com/golang/text", - branch: nil, - ref: "v0.2.0" - } - }] - end - - its(:length) { is_expected.to eq(1) } - - describe "the updated manifest" do - subject(:updated_manifest_content) do - updated_files.find { |f| f.name == "Gopkg.toml" }.content - end - - it "includes the new tag" do - expect(updated_manifest_content). - to end_with(" name = \"golang.org/x/text\"\n"\ - " version = \"^0.3.0\"\n") + to include(%(version = ">= 1.0.0, < 4.0.0")) end end end From 4eb151562acbd99ac6da37e1b73763dcc1cb7e6f Mon Sep 17 00:00:00 2001 From: Grey Baker Date: Wed, 25 Jul 2018 13:38:36 +0100 Subject: [PATCH 28/28] Dep: First working version of LockfileUpdater --- lib/dependabot/file_updaters/go/dep.rb | 9 +- .../file_updaters/go/dep/lockfile_updater.rb | 187 ++++++++++++++++++ .../file_updaters/go/dep/manifest_updater.rb | 1 - lib/dependabot/update_checkers/go/dep.rb | 3 +- .../go/dep/latest_version_finder.rb | 2 - .../go/gopkg_locks/custom_source.lock | 22 +++ .../go/gopkg_tomls/custom_source.toml | 14 ++ 7 files changed, 230 insertions(+), 8 deletions(-) create mode 100644 lib/dependabot/file_updaters/go/dep/lockfile_updater.rb create mode 100644 spec/fixtures/go/gopkg_locks/custom_source.lock create mode 100644 spec/fixtures/go/gopkg_tomls/custom_source.toml diff --git a/lib/dependabot/file_updaters/go/dep.rb b/lib/dependabot/file_updaters/go/dep.rb index eb5ed471d0f..6c1f9ffd17a 100644 --- a/lib/dependabot/file_updaters/go/dep.rb +++ b/lib/dependabot/file_updaters/go/dep.rb @@ -8,6 +8,7 @@ module FileUpdaters module Go class Dep < Dependabot::FileUpdaters::Base require_relative "dep/manifest_updater" + require_relative "dep/lockfile_updater" def self.updated_files_regex [ @@ -59,9 +60,11 @@ def updated_manifest_content end def updated_lockfile_content - # TODO: This normally needs to be written in the native language. - # We do so by shelling out to a helper method (see other languages) - lockfile.content + LockfileUpdater.new( + dependencies: dependencies, + dependency_files: dependency_files, + credentials: credentials + ).updated_lockfile_content end end end diff --git a/lib/dependabot/file_updaters/go/dep/lockfile_updater.rb b/lib/dependabot/file_updaters/go/dep/lockfile_updater.rb new file mode 100644 index 00000000000..3ebe66c7ff0 --- /dev/null +++ b/lib/dependabot/file_updaters/go/dep/lockfile_updater.rb @@ -0,0 +1,187 @@ +# frozen_string_literal: true + +require "toml-rb" + +require "dependabot/shared_helpers" +require "dependabot/dependency_file" +require "dependabot/file_updaters/go/dep" +require "dependabot/file_parsers/go/dep" + +module Dependabot + module FileUpdaters + module Go + class Dep + class LockfileUpdater + def initialize(dependencies:, dependency_files:, credentials:) + @dependencies = dependencies + @dependency_files = dependency_files + @credentials = credentials + end + + def updated_lockfile_content + updated_content = + Dir.chdir(go_dir) do + write_temporary_dependency_files + + SharedHelpers.with_git_configured(credentials: credentials) do + # Shell out to dep, which handles everything for us, and does + # so without doing an install (so it's fast). + command = "dep ensure -update --no-vendor "\ + "#{dependencies.map(&:name).join(' ')}" + run_shell_command(command) + end + + File.read("Gopkg.lock") + end + + FileUtils.rm_rf(go_dir) + updated_content + end + + private + + attr_reader :dependencies, :dependency_files, :credentials + + def run_shell_command(command) + raw_response = nil + IO.popen(command, err: %i(child out)) do |process| + raw_response = process.read + end + + # Raise an error with the output from the shell session if dep + # returns a non-zero status + return if $CHILD_STATUS.success? + raise SharedHelpers::HelperSubprocessFailed.new( + raw_response, + command + ) + end + + def write_temporary_dependency_files + dependency_files.each do |file| + path = file.name + FileUtils.mkdir_p(Pathname.new(path).dirname) + File.write(file.name, file.content) + end + + # Overwrite the manifest with our custom prepared one + File.write(prepared_manifest.name, prepared_manifest.content) + + File.write("hello.go", dummy_app_content) + end + + def prepared_manifest + DependencyFile.new( + name: manifest.name, + content: prepared_manifest_content + ) + end + + def prepared_manifest_content + parsed_manifest = TomlRB.parse(manifest.content) + + dependencies.each do |dep| + req = dep.requirements.find { |r| r[:file] == manifest.name } + + if req + update_constraint!(parsed_manifest, dep) + else + create_constraint!(parsed_manifest, dep) + end + end + + TomlRB.dump(parsed_manifest) + end + + # Used to lock the version when updating a top-level dependency + def update_constraint!(parsed_manifest, dep) + details = + parsed_manifest. + values_at(*FileParsers::Go::Dep::REQUIREMENT_TYPES). + flatten.compact.find { |d| d["name"] == dep.name } + + req = dep.requirements.find { |r| r[:file] == manifest.name } + + details.delete("branch") + + if req.fetch(:source).fetch(:type) == "git" + details.delete("version") + details["revision"] = dep.version + else + details.delete("revision") + details["version"] = dep.version + end + end + + # Used to lock the version when updating a subdependency + def create_constraint!(parsed_manifest, dep) + details = { "name" => dep.name } + + # Fetch the details from the lockfile to check whether this + # sub-dependency needs a git revision or a version. + original_details = + parsed_file(lockfile).fetch("projects"). + find { |p| p["name"] == dep.name } + + if original_details["source"] + details["source"] = original_details["source"] + end + + if original_details["version"] + details["version"] = dep.version + else + details["revision"] = dep.version + end + + parsed_manifest["constraint"] << details + end + + def go_dir + # Work in a directory called "$HOME/go/src/dependabot-tmp". + # TODO: This should pick up what the user's actual GOPATH is. + go_dir = File.join(Dir.home, "go", "src", "dependabot-tmp") + FileUtils.mkdir_p(go_dir) + go_dir + end + + def dummy_app_content + base = "package main\n\n"\ + "import \"fmt\"\n\n" + + dependencies_to_import.each { |nm| base += "import \"#{nm}\"\n\n" } + + base + "func main() {\n fmt.Printf(\"hello, world\\n\")\n}" + end + + def dependencies_to_import + # There's no way to tell whether dependencies that appear in the + # lockfile are there because they're imported themselves or because + # they're sub-dependencies of something else. v0.5.0 will fix that + # problem, but for now we just have to import everything. + # + # NOTE: This means the `inputs-digest` we generate will be wrong. + # That's a pity, but we'd have to iterate through too many + # possibilities to get it right. Again, this is fixed in v0.5.0. + return [] unless lockfile + TomlRB.parse(lockfile.content).fetch("projects").map do |detail| + detail["name"] + end + end + + def parsed_file(file) + @parsed_file ||= {} + @parsed_file[file.name] ||= TomlRB.parse(file.content) + end + + def manifest + @manifest ||= dependency_files.find { |f| f.name == "Gopkg.toml" } + end + + def lockfile + @lockfile ||= dependency_files.find { |f| f.name == "Gopkg.lock" } + end + end + end + end + end +end diff --git a/lib/dependabot/file_updaters/go/dep/manifest_updater.rb b/lib/dependabot/file_updaters/go/dep/manifest_updater.rb index 6503e237c21..824d6e07678 100644 --- a/lib/dependabot/file_updaters/go/dep/manifest_updater.rb +++ b/lib/dependabot/file_updaters/go/dep/manifest_updater.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require "parser/current" require "dependabot/file_updaters/go/dep" module Dependabot diff --git a/lib/dependabot/update_checkers/go/dep.rb b/lib/dependabot/update_checkers/go/dep.rb index 31d35d00499..ad69850d397 100644 --- a/lib/dependabot/update_checkers/go/dep.rb +++ b/lib/dependabot/update_checkers/go/dep.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +require "toml-rb" require "dependabot/update_checkers/base" module Dependabot @@ -211,8 +212,6 @@ def git_branch_or_ref_in_release?(release) def parsed_file(file) @parsed_file ||= {} @parsed_file[file.name] ||= TomlRB.parse(file.content) - rescue TomlRB::ParseError - raise Dependabot::DependencyFileNotParseable, file.path end def manifest diff --git a/lib/dependabot/update_checkers/go/dep/latest_version_finder.rb b/lib/dependabot/update_checkers/go/dep/latest_version_finder.rb index 89df3f80c9d..a3c899bb3c6 100644 --- a/lib/dependabot/update_checkers/go/dep/latest_version_finder.rb +++ b/lib/dependabot/update_checkers/go/dep/latest_version_finder.rb @@ -131,8 +131,6 @@ def git_commit_checker def parsed_file(file) @parsed_file ||= {} @parsed_file[file.name] ||= TomlRB.parse(file.content) - rescue TomlRB::ParseError - raise Dependabot::DependencyFileNotParseable, file.path end def version_class diff --git a/spec/fixtures/go/gopkg_locks/custom_source.lock b/spec/fixtures/go/gopkg_locks/custom_source.lock new file mode 100644 index 00000000000..8d026e4e5fe --- /dev/null +++ b/spec/fixtures/go/gopkg_locks/custom_source.lock @@ -0,0 +1,22 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + name = "github.com/bsm/ratelimit" + packages = ["."] + revision = "4a38dca83ebadb017e83301642fab61b1c17edb7" + version = "v2.0.0" + +[[projects]] + branch = "master" + name = "github.com/some/thing" + packages = ["."] + revision = "0b96aaa707760d6ab28d9b9d1913ff5993328bae" + source = "https://github.com/dgrijalva/jwt-go" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "677a622bfbc921ebb12df377c21012ff65974214b5d13a8fe77b2156c8d2f832" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/spec/fixtures/go/gopkg_tomls/custom_source.toml b/spec/fixtures/go/gopkg_tomls/custom_source.toml new file mode 100644 index 00000000000..11c372a7088 --- /dev/null +++ b/spec/fixtures/go/gopkg_tomls/custom_source.toml @@ -0,0 +1,14 @@ +required = [ + "github.com/some/thing", + "github.com/bsm/ratelimit" + ] + +[[constraint]] + name = "github.com/some/thing" + branch = "master" + source = "https://github.com/dgrijalva/jwt-go" + +[[constraint]] + name = "github.com/bsm/ratelimit" + version = "2.0.0" +