From ccceab0b17b47433b086117050bf55602f86ccc9 Mon Sep 17 00:00:00 2001 From: Chris Stylianou Date: Wed, 26 Jun 2019 18:55:31 +0100 Subject: [PATCH] Implemented the Azure client for file fetcher/pull request creator --- common/lib/dependabot/clients/azure.rb | 213 +++++++++++++++ common/lib/dependabot/file_fetchers/base.rb | 32 +++ common/lib/dependabot/pull_request_creator.rb | 16 ++ .../dependabot/pull_request_creator/azure.rb | 92 +++++++ .../pull_request_creator/labeler.rb | 12 + .../pull_request_creator/message_builder.rb | 11 +- .../pull_request_creator/pr_name_prefixer.rb | 43 ++- .../dependabot/file_fetchers/base_spec.rb | 231 ++++++++++++++++ .../pull_request_creator/azure_spec.rb | 250 ++++++++++++++++++ .../dependabot/pull_request_creator_spec.rb | 23 ++ .../spec/fixtures/azure/branch_not_found.json | 4 + common/spec/fixtures/azure/bump_repo.json | 49 ++++ .../spec/fixtures/azure/business_files.json | 37 +++ .../spec/fixtures/azure/business_folder.json | 19 ++ .../fixtures/azure/commits_with_existing.json | 26 ++ .../spec/fixtures/azure/default_branch.json | 23 ++ common/spec/fixtures/azure/gemspec_content | 61 +++++ common/spec/fixtures/azure/master_branch.json | 25 ++ common/spec/fixtures/azure/no_files.json | 29 ++ common/spec/fixtures/azure/other_branch.json | 25 ++ common/spec/fixtures/azure/pull_request.json | 10 + 21 files changed, 1226 insertions(+), 5 deletions(-) create mode 100644 common/lib/dependabot/clients/azure.rb create mode 100644 common/lib/dependabot/pull_request_creator/azure.rb create mode 100644 common/spec/dependabot/pull_request_creator/azure_spec.rb create mode 100644 common/spec/fixtures/azure/branch_not_found.json create mode 100644 common/spec/fixtures/azure/bump_repo.json create mode 100644 common/spec/fixtures/azure/business_files.json create mode 100644 common/spec/fixtures/azure/business_folder.json create mode 100644 common/spec/fixtures/azure/commits_with_existing.json create mode 100644 common/spec/fixtures/azure/default_branch.json create mode 100644 common/spec/fixtures/azure/gemspec_content create mode 100644 common/spec/fixtures/azure/master_branch.json create mode 100644 common/spec/fixtures/azure/no_files.json create mode 100644 common/spec/fixtures/azure/other_branch.json create mode 100644 common/spec/fixtures/azure/pull_request.json diff --git a/common/lib/dependabot/clients/azure.rb b/common/lib/dependabot/clients/azure.rb new file mode 100644 index 0000000000..723659984d --- /dev/null +++ b/common/lib/dependabot/clients/azure.rb @@ -0,0 +1,213 @@ +# frozen_string_literal: true + +require "dependabot/shared_helpers" +require "excon" + +module Dependabot + module Clients + class Azure + class NotFound < StandardError; end + + ####################### + # Constructor methods # + ####################### + + def self.for_source(source:, credentials:) + credential = + credentials. + select { |cred| cred["type"] == "git_source" }. + find { |cred| cred["host"] == source.hostname } + + new(source, credential) + end + + ########## + # Client # + ########## + + def initialize(source, credentials) + @source = source + @credentials = credentials + end + + def fetch_commit(_repo, branch) + response = get(source.api_endpoint + + source.organization + "/" + source.project + + "/_apis/git/repositories/" + source.unscoped_repo + + "/stats/branches?name=" + branch) + + JSON.parse(response.body).fetch("commit").fetch("commitId") + end + + def fetch_default_branch(_repo) + response = get(source.api_endpoint + + source.organization + "/" + source.project + + "/_apis/git/repositories/" + source.unscoped_repo) + + JSON.parse(response.body).fetch("defaultBranch").gsub("refs/heads/", "") + end + + def fetch_repo_contents(commit = nil, path = nil) + tree = fetch_repo_contents_treeroot(commit, path) + + response = get(source.api_endpoint + + source.organization + "/" + source.project + + "/_apis/git/repositories/" + source.unscoped_repo + + "/trees/" + tree + "?recursive=false") + + JSON.parse(response.body).fetch("treeEntries") + end + + def fetch_repo_contents_treeroot(commit = nil, path = nil) + actual_path = path + actual_path = "/" if path.to_s.empty? + + tree_url = source.api_endpoint + + source.organization + "/" + source.project + + "/_apis/git/repositories/" + source.unscoped_repo + + "/items?path=" + actual_path + + unless commit.to_s.empty? + tree_url += "&versionDescriptor.versionType=commit" \ + "&versionDescriptor.version=" + commit + end + + tree_response = get(tree_url) + + JSON.parse(tree_response.body).fetch("objectId") + end + + def fetch_file_contents(commit, path) + response = get(source.api_endpoint + + source.organization + "/" + source.project + + "/_apis/git/repositories/" + source.unscoped_repo + + "/items?path=" + path + + "&versionDescriptor.versionType=commit" \ + "&versionDescriptor.version=" + commit) + + response.body + end + + def commits(branch_name = nil) + commits_url = source.api_endpoint + + source.organization + "/" + source.project + + "/_apis/git/repositories/" + source.unscoped_repo + + "/commits" + + unless branch_name.to_s.empty? + commits_url += "?searchCriteria.itemVersion.version=" + branch_name + end + + response = get(commits_url) + + JSON.parse(response.body).fetch("value") + end + + def branch(branch_name) + response = get(source.api_endpoint + + source.organization + "/" + source.project + + "/_apis/git/repositories/" + source.unscoped_repo + + "/refs?filter=heads/" + branch_name) + + JSON.parse(response.body).fetch("value").first + end + + def pull_requests(source_branch, target_branch) + response = get(source.api_endpoint + + source.organization + "/" + source.project + + "/_apis/git/repositories/" + source.unscoped_repo + + "/pullrequests?searchCriteria.status=all" \ + "&searchCriteria.sourceRefName=refs/heads/" + source_branch + + "&searchCriteria.targetRefName=refs/heads/" + target_branch) + + JSON.parse(response.body).fetch("value") + end + + def create_commit(branch_name, base_commit, commit_message, files) + content = { + refUpdates: [ + { name: "refs/heads/" + branch_name, oldObjectId: base_commit } + ], + commits: [ + { + comment: commit_message, + changes: files.map do |file| + { + changeType: "edit", + item: { path: file.path }, + newContent: { + content: Base64.encode64(file.content), + contentType: "base64encoded" + } + } + end + } + ] + } + + post(source.api_endpoint + source.organization + "/" + source.project + + "/_apis/git/repositories/" + source.unscoped_repo + + "/pushes?api-version=5.0", content.to_json) + end + + def create_pull_request(pr_name, source_branch, target_branch, + pr_description, labels) + # Azure DevOps only support descriptions up to 4000 characters (https://developercommunity.visualstudio.com/content/problem/608770/remove-4000-character-limit-on-pull-request-descri.html) + azure_max_length = 3999 + if pr_description.length > azure_max_length + truncated_msg = "...\n\n_Description has been truncated_" + truncate_length = azure_max_length - truncated_msg.length + pr_description = pr_description[0..truncate_length] + truncated_msg + end + + content = { + sourceRefName: "refs/heads/" + source_branch, + targetRefName: "refs/heads/" + target_branch, + title: pr_name, + description: pr_description, + labels: labels.map { |label| { name: label } } + } + + post(source.api_endpoint + + source.organization + "/" + source.project + + "/_apis/git/repositories/" + source.unscoped_repo + + "/pullrequests?api-version=5.0", content.to_json) + end + + def get(url) + response = Excon.get( + url, + user: credentials&.fetch("username"), + password: credentials&.fetch("password"), + idempotent: true, + **SharedHelpers.excon_defaults + ) + raise NotFound if response.status == 404 + + response + end + + def post(url, json) + response = Excon.post( + url, + headers: { + "Content-Type" => "application/json" + }, + body: json, + user: credentials&.fetch("username"), + password: credentials&.fetch("password"), + idempotent: true, + **SharedHelpers.excon_defaults + ) + raise NotFound if response.status == 404 + + response + end + + private + + attr_reader :credentials + attr_reader :source + end + end +end diff --git a/common/lib/dependabot/file_fetchers/base.rb b/common/lib/dependabot/file_fetchers/base.rb index 92a965b5b8..17e394d6a6 100644 --- a/common/lib/dependabot/file_fetchers/base.rb +++ b/common/lib/dependabot/file_fetchers/base.rb @@ -3,6 +3,7 @@ require "dependabot/dependency_file" require "dependabot/source" require "dependabot/errors" +require "dependabot/clients/azure" require "dependabot/clients/github_with_retries" require "dependabot/clients/bitbucket_with_retries" require "dependabot/clients/gitlab_with_retries" @@ -17,6 +18,7 @@ class Base CLIENT_NOT_FOUND_ERRORS = [ Octokit::NotFound, Gitlab::Error::NotFound, + Dependabot::Clients::Azure::NotFound, Dependabot::Clients::Bitbucket::NotFound ].freeze @@ -152,6 +154,8 @@ def _fetch_repo_contents_fully_specified(provider, repo, path, commit) _github_repo_contents(repo, path, commit) when "gitlab" _gitlab_repo_contents(repo, path, commit) + when "azure" + _azure_repo_contents(path, commit) when "bitbucket" _bitbucket_repo_contents(repo, path, commit) else raise "Unsupported provider '#{provider}'." @@ -222,6 +226,25 @@ def _gitlab_repo_contents(repo, path, commit) end end + def _azure_repo_contents(path, commit) + response = azure_client.fetch_repo_contents(commit, path) + + response.map do |entry| + type = case entry.fetch("gitObjectType") + when "blob" then "file" + when "tree" then "dir" + else entry.fetch("gitObjectType") + end + + OpenStruct.new( + name: File.basename(entry.fetch("relativePath")), + path: entry.fetch("relativePath"), + type: type, + size: entry.fetch("size") + ) + end + end + def _bitbucket_repo_contents(repo, path, commit) response = bitbucket_client.fetch_repo_contents( repo, @@ -299,6 +322,8 @@ def _fetch_file_content_fully_specified(provider, repo, path, commit) when "gitlab" tmp = gitlab_client.get_file(repo, path, commit).content Base64.decode64(tmp).force_encoding("UTF-8").encode + when "azure" + azure_client.fetch_file_contents(commit, path) when "bitbucket" bitbucket_client.fetch_file_contents(repo, commit, path) else raise "Unsupported provider '#{source.provider}'." @@ -374,6 +399,7 @@ def client_for_provider case source.provider when "github" then github_client when "gitlab" then gitlab_client + when "azure" then azure_client when "bitbucket" then bitbucket_client else raise "Unsupported provider '#{source.provider}'." end @@ -395,6 +421,12 @@ def gitlab_client ) end + def azure_client + @azure_client ||= + Dependabot::Clients::Azure. + for_source(source: source, credentials: credentials) + end + def bitbucket_client # TODO: When self-hosted Bitbucket is supported this should use # `Bitbucket.for_source` diff --git a/common/lib/dependabot/pull_request_creator.rb b/common/lib/dependabot/pull_request_creator.rb index 2fe90d40e3..0966107841 100644 --- a/common/lib/dependabot/pull_request_creator.rb +++ b/common/lib/dependabot/pull_request_creator.rb @@ -4,6 +4,7 @@ module Dependabot class PullRequestCreator + require "dependabot/pull_request_creator/azure" require "dependabot/pull_request_creator/github" require "dependabot/pull_request_creator/gitlab" require "dependabot/pull_request_creator/message_builder" @@ -68,6 +69,7 @@ def create case source.provider when "github" then github_creator.create when "gitlab" then gitlab_creator.create + when "azure" then azure_creator.create else raise "Unsupported provider #{source.provider}" end end @@ -120,6 +122,20 @@ def gitlab_creator ) end + def azure_creator + Azure.new( + source: source, + branch_name: branch_namer.new_branch_name, + base_commit: base_commit, + credentials: credentials, + files: files, + commit_message: message_builder.commit_message, + pr_description: message_builder.pr_message, + pr_name: message_builder.pr_name, + labeler: labeler + ) + end + def message_builder @message_builder || MessageBuilder.new( diff --git a/common/lib/dependabot/pull_request_creator/azure.rb b/common/lib/dependabot/pull_request_creator/azure.rb new file mode 100644 index 0000000000..9168dfadf3 --- /dev/null +++ b/common/lib/dependabot/pull_request_creator/azure.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +require "dependabot/clients/azure" +require "dependabot/pull_request_creator" + +module Dependabot + class PullRequestCreator + class Azure + attr_reader :source, :branch_name, :base_commit, :credentials, + :files, :commit_message, :pr_description, :pr_name, + :labeler + + def initialize(source:, branch_name:, base_commit:, credentials:, + files:, commit_message:, pr_description:, pr_name:, + labeler:) + @source = source + @branch_name = branch_name + @base_commit = base_commit + @credentials = credentials + @files = files + @commit_message = commit_message + @pr_description = pr_description + @pr_name = pr_name + @labeler = labeler + end + + def create + return if branch_exists? && pull_request_exists? + + create_commit unless branch_exists? && commit_exists? + + create_pull_request + end + + private + + def azure_client_for_source + @azure_client_for_source ||= + Dependabot::Clients::Azure.for_source( + source: source, + credentials: credentials + ) + end + + def branch_exists? + @branch_ref ||= + azure_client_for_source.branch(branch_name) + + @branch_ref + rescue ::Azure::Error::NotFound + false + end + + def commit_exists? + @commits ||= + azure_client_for_source.commits(branch_name) + commit_message.start_with?(@commits.first.fetch("comment")) + end + + def pull_request_exists? + azure_client_for_source.pull_requests( + branch_name, + source.branch || default_branch + ).any? + end + + def create_commit + azure_client_for_source.create_commit( + branch_name, + base_commit, + commit_message, + files + ) + end + + def create_pull_request + azure_client_for_source.create_pull_request( + pr_name, + branch_name, + source.branch || default_branch, + pr_description, + labeler.labels_for_pr + ) + end + + def default_branch + @default_branch ||= + azure_client_for_source.fetch_default_branch(source.repo) + end + end + end +end diff --git a/common/lib/dependabot/pull_request_creator/labeler.rb b/common/lib/dependabot/pull_request_creator/labeler.rb index 1e9c006b92..b1ce4d4f54 100644 --- a/common/lib/dependabot/pull_request_creator/labeler.rb +++ b/common/lib/dependabot/pull_request_creator/labeler.rb @@ -221,6 +221,7 @@ def labels case source.provider when "github" then fetch_github_labels when "gitlab" then fetch_gitlab_labels + when "azure" then fetch_azure_labels else raise "Unsupported provider #{source.provider}" end end @@ -251,10 +252,19 @@ def fetch_gitlab_labels map(&:name) end + def fetch_azure_labels + langauge_name = + self.class.label_details_for_package_manager(package_manager). + fetch(:name) + + @labels = [*@labels, "dependencies", "security", langauge_name].uniq + end + def create_dependencies_label case source.provider when "github" then create_github_dependencies_label when "gitlab" then create_gitlab_dependencies_label + when "azure" then @labels # Azure does not have centralised labels else raise "Unsupported provider #{source.provider}" end end @@ -263,6 +273,7 @@ def create_security_label case source.provider when "github" then create_github_security_label when "gitlab" then create_gitlab_security_label + when "azure" then @labels # Azure does not have centralised labels else raise "Unsupported provider #{source.provider}" end end @@ -271,6 +282,7 @@ def create_language_label case source.provider when "github" then create_github_language_label when "gitlab" then create_gitlab_language_label + when "azure" then @labels # Azure does not have centralised labels else raise "Unsupported provider #{source.provider}" end end diff --git a/common/lib/dependabot/pull_request_creator/message_builder.rb b/common/lib/dependabot/pull_request_creator/message_builder.rb index 17d948df05..f82f7187d4 100644 --- a/common/lib/dependabot/pull_request_creator/message_builder.rb +++ b/common/lib/dependabot/pull_request_creator/message_builder.rb @@ -415,9 +415,14 @@ def maintainer_changes_cascade(dep) end def build_details_tag(summary:, body:) - msg = "\n
\n#{summary}\n\n" - msg += body - msg + "
" + # Azure DevOps does not support
tag (https://developercommunity.visualstudio.com/content/problem/608769/add-support-for-in-markdown.html) + if source.provider == "azure" + "\n\##{summary}\n\n#{body}" + else + msg = "\n
\n#{summary}\n\n" + msg += body + msg + "
" + end end def serialized_vulnerability_details(details) diff --git a/common/lib/dependabot/pull_request_creator/pr_name_prefixer.rb b/common/lib/dependabot/pull_request_creator/pr_name_prefixer.rb index 13151c7981..50e1b3d685 100644 --- a/common/lib/dependabot/pull_request_creator/pr_name_prefixer.rb +++ b/common/lib/dependabot/pull_request_creator/pr_name_prefixer.rb @@ -1,9 +1,11 @@ # frozen_string_literal: true +require "dependabot/clients/azure" require "dependabot/clients/github_with_retries" require "dependabot/clients/gitlab_with_retries" require "dependabot/pull_request_creator" +# rubocop:disable Metrics/ClassLength module Dependabot class PullRequestCreator class PrNamePrefixer @@ -223,10 +225,15 @@ def recent_commit_messages case source.provider when "github" then recent_github_commit_messages when "gitlab" then recent_gitlab_commit_messages + when "azure" then recent_azure_commit_messages else raise "Unsupported provider: #{source.provider}" end end + def dependabot_email + "support@dependabot.com" + end + def recent_github_commit_messages recent_github_commits. reject { |c| c.author&.type == "Bot" }. @@ -242,17 +249,30 @@ def recent_gitlab_commit_messages gitlab_client_for_source.commits(source.repo) @recent_gitlab_commit_messages. - reject { |c| c.author_email == "support@dependabot.com" }. + reject { |c| c.author_email == dependabot_email }. reject { |c| c.message&.start_with?("merge !") }. map(&:message). compact. map(&:strip) end + def recent_azure_commit_messages + @recent_azure_commit_messages ||= + azure_client_for_source.commits + + @recent_azure_commit_messages. + reject { |c| c.fetch("author").fetch("email") == dependabot_email }. + reject { |c| c.fetch("comment")&.start_with?("Merge") }. + map { |c| c.fetch("comment") }. + compact. + map(&:strip) + end + def last_dependabot_commit_message case source.provider when "github" then last_github_dependabot_commit_message when "gitlab" then last_gitlab_dependabot_commit_message + when "azure" then last_azure_dependabot_commit_message else raise "Unsupported provider: #{source.provider}" end end @@ -278,7 +298,17 @@ def last_gitlab_dependabot_commit_message gitlab_client_for_source.commits(source.repo) @recent_gitlab_commit_messages. - find { |c| c.author_email == "support@dependabot.com" }&. + find { |c| c.author_email == dependabot_email }&. + message&. + strip + end + + def last_azure_dependabot_commit_message + @recent_azure_commit_messages ||= + azure_client_for_source.commits + + @recent_azure_commit_messages. + find { |c| c.fetch("author").fetch("email") == dependabot_email }&. message&. strip end @@ -299,9 +329,18 @@ def gitlab_client_for_source ) end + def azure_client_for_source + @azure_client_for_source ||= + Dependabot::Clients::Azure.for_source( + source: source, + credentials: credentials + ) + end + def package_manager @package_manager ||= dependencies.first.package_manager end end end end +# rubocop:enable Metrics/ClassLength diff --git a/common/spec/dependabot/file_fetchers/base_spec.rb b/common/spec/dependabot/file_fetchers/base_spec.rb index 6e45811af9..6343b52ca0 100644 --- a/common/spec/dependabot/file_fetchers/base_spec.rb +++ b/common/spec/dependabot/file_fetchers/base_spec.rb @@ -196,6 +196,41 @@ def fetch_files it { is_expected.to eq("4c2ea65f2eb932c438557cb6ec29b984794c6108") } end end + + context "with a Azure DevOps source" do + let(:provider) { "azure" } + let(:repo) { "org/gocardless/_git/bump" } + let(:base_url) { "https://dev.azure.com/org/gocardless" } + let(:repo_url) { base_url + "/_apis/git/repositories/bump" } + let(:branch_url) { repo_url + "/stats/branches?name=master" } + + before do + stub_request(:get, repo_url). + to_return(status: 200, + body: fixture("azure", "bump_repo.json"), + headers: { "content-type" => "application/json" }) + stub_request(:get, branch_url). + to_return(status: 200, + body: fixture("azure", "master_branch.json"), + headers: { "content-type" => "application/json" }) + end + + it { is_expected.to eq("9c8376e9b2e943c2c72fac4b239876f377f0305a") } + + context "with a target branch" do + let(:branch) { "my_branch" } + let(:branch_url) { repo_url + "/stats/branches?name=my_branch" } + + before do + stub_request(:get, branch_url). + to_return(status: 200, + body: fixture("azure", "other_branch.json"), + headers: { "content-type" => "application/json" }) + end + + it { is_expected.to eq("8c8376e9b2e943c2c72fac4b239876f377f0305b") } + end + end end describe "#files" do @@ -856,6 +891,202 @@ def fetch_files end end + context "with a Azure DevOps source" do + let(:provider) { "azure" } + let(:repo) { "org/gocardless/_git/bump" } + let(:base_url) { "https://dev.azure.com/org/gocardless" } + let(:repo_url) { base_url + "/_apis/git/repositories/bump" } + let(:url) do + repo_url + "/items?path=requirements.txt" \ + "&versionDescriptor.version=sha&versionDescriptor.versionType=commit" + end + + before do + stub_request(:get, url). + to_return(status: 200, + body: fixture("azure", "gemspec_content"), + headers: { "content-type" => "text/plain" }) + end + + its(:length) { is_expected.to eq(1) } + + describe "the file" do + subject { files.find { |file| file.name == "requirements.txt" } } + + it { is_expected.to be_a(Dependabot::DependencyFile) } + its(:content) { is_expected.to include("required_rubygems_version") } + end + + context "with a directory specified" do + let(:file_fetcher_instance) do + child_class.new(source: source, credentials: credentials) + end + + context "that ends in a slash" do + let(:directory) { "app/" } + let(:url) do + repo_url + "/items?path=app/requirements.txt" \ + "&versionDescriptor.version=sha" \ + "&versionDescriptor.versionType=commit" + end + + it "hits the right Azure DevOps URL" do + files + expect(WebMock).to have_requested(:get, url) + end + end + + context "that begins with a slash" do + let(:directory) { "/app" } + let(:url) do + repo_url + "/items?path=app/requirements.txt" \ + "&versionDescriptor.version=sha" \ + "&versionDescriptor.versionType=commit" + end + + it "hits the right Azure DevOps URL" do + files + expect(WebMock).to have_requested(:get, url) + end + end + + context "that includes a slash" do + let(:directory) { "a/pp" } + let(:url) do + repo_url + "/items?path=a/pp/requirements.txt" \ + "&versionDescriptor.version=sha" \ + "&versionDescriptor.versionType=commit" + end + + it "hits the right Azure DevOps URL" do + files + expect(WebMock).to have_requested(:get, url) + end + end + end + + context "when a dependency file can't be found" do + before do + stub_request(:get, url). + to_return( + status: 404, + body: fixture("bitbucket", "file_not_found.json"), + headers: { "content-type" => "application/json" } + ) + end + + it "raises a custom error" do + expect { file_fetcher_instance.files }. + to raise_error(Dependabot::DependencyFileNotFound) do |error| + expect(error.file_path).to eq("/requirements.txt") + end + end + end + + context "when fetching the file only if present" do + let(:child_class) do + Class.new(described_class) do + def self.required_files_in?(filenames) + filenames.include?("requirements.txt") + end + + def self.required_files_message + "Repo must contain a requirements.txt." + end + + private + + def fetch_files + [fetch_file_if_present("requirements.txt")].compact + end + end + end + + let(:repo_contents_tree_url) do + repo_url + "/items?path=/&versionDescriptor.version=sha" \ + "&versionDescriptor.versionType=commit" + end + let(:repo_contents_url) do + repo_url + "/trees/9fea8a9fd1877daecde8f80137f9dfee6ec0b01a" \ + "?recursive=false" + end + let(:repo_file_url) do + repo_url + "/items?path=requirements.txt" \ + "&versionDescriptor.version=sha" \ + "&versionDescriptor.versionType=commit" + end + + before do + stub_request(:get, repo_contents_tree_url). + to_return(status: 200, + body: fixture("azure", "business_folder.json"), + headers: { "content-type" => "text/plain" }) + stub_request(:get, repo_contents_url). + to_return(status: 200, + body: fixture("azure", "business_files.json"), + headers: { "content-type" => "application/json" }) + stub_request(:get, repo_file_url). + to_return(status: 200, + body: fixture("azure", "gemspec_content"), + headers: { "content-type" => "text/plain" }) + end + + its(:length) { is_expected.to eq(1) } + + describe "the file" do + subject { files.find { |file| file.name == "requirements.txt" } } + + it { is_expected.to be_a(Dependabot::DependencyFile) } + its(:content) { is_expected.to include("required_rubygems_version") } + end + + context "that can't be found" do + before do + stub_request(:get, repo_contents_url). + to_return(status: 200, + body: fixture("azure", "no_files.json"), + headers: { "content-type" => "application/json" }) + end + + its(:length) { is_expected.to eq(0) } + end + + context "with a directory" do + let(:directory) { "/app" } + + let(:repo_contents_tree_url) do + repo_url + "/items?path=app&versionDescriptor.version=sha" \ + "&versionDescriptor.versionType=commit" + end + let(:repo_contents_url) do + repo_url + "/trees/9fea8a9fd1877daecde8f80137f9dfee6ec0b01a" \ + "?recursive=false" + end + + before do + stub_request(:get, repo_contents_tree_url). + to_return(status: 200, + body: fixture("azure", "business_folder.json"), + headers: { "content-type" => "text/plain" }) + stub_request(:get, repo_contents_url). + to_return(status: 200, + body: fixture("azure", "no_files.json"), + headers: { "content-type" => "application/json" }) + end + + let(:url) do + repo_url + "/items?path=app&versionDescriptor.version=sha" \ + "&versionDescriptor.versionType=commit" + end + + it "hits the right Azure DevOps URL" do + files + expect(WebMock).to have_requested(:get, url) + end + end + end + end + context "with an interesting filename" do let(:file_fetcher_instance) do child_class.new(source: source, credentials: credentials) diff --git a/common/spec/dependabot/pull_request_creator/azure_spec.rb b/common/spec/dependabot/pull_request_creator/azure_spec.rb new file mode 100644 index 0000000000..0be0f9691a --- /dev/null +++ b/common/spec/dependabot/pull_request_creator/azure_spec.rb @@ -0,0 +1,250 @@ +# frozen_string_literal: true + +require "spec_helper" +require "dependabot/dependency" +require "dependabot/dependency_file" +require "dependabot/pull_request_creator/azure" + +RSpec.describe Dependabot::PullRequestCreator::Azure do + subject(:creator) do + described_class.new( + source: source, + branch_name: branch_name, + base_commit: base_commit, + credentials: credentials, + files: files, + commit_message: commit_message, + pr_description: pr_description, + pr_name: pr_name, + labeler: labeler + ) + end + + let(:source) do + Dependabot::Source.new(provider: "azure", repo: "org/gocardless/_git/bump") + end + let(:branch_name) { "dependabot/bundler/business-1.5.0" } + let(:base_commit) { "basecommitsha" } + let(:credentials) do + [{ + "type" => "git_source", + "host" => "dev.azure.com", + "username" => "x-access-token", + "password" => "token" + }] + end + let(:files) { [gemfile, gemfile_lock] } + let(:commit_message) { "Commit msg" } + let(:pr_description) { "PR msg" } + let(:pr_name) { "PR name" } + let(:author_details) { nil } + let(:approvers) { nil } + let(:assignee) { nil } + let(:milestone) { nil } + let(:dep_source) do + Dependabot::Source.new(provider: "github", repo: "gocardless/bump") + end + let(:labeler) do + Dependabot::PullRequestCreator::Labeler.new( + source: dep_source, + credentials: credentials, + custom_labels: custom_labels, + includes_security_fixes: false, + dependencies: [dependency], + label_language: false, + automerge_candidate: false + ) + end + let(:custom_labels) { nil } + let(:dependency) do + Dependabot::Dependency.new( + name: "business", + version: "1.5.0", + previous_version: "1.4.0", + package_manager: "bundler", + requirements: [], + previous_requirements: [] + ) + end + + let(:gemfile) do + Dependabot::DependencyFile.new( + name: "Gemfile", + content: fixture("ruby", "gemfiles", "Gemfile") + ) + end + let(:gemfile_lock) do + Dependabot::DependencyFile.new( + name: "Gemfile.lock", + content: fixture("ruby", "gemfiles", "Gemfile") + ) + end + + let(:json_header) { { "Content-Type" => "application/json" } } + let(:repo_api_url) do + "https://dev.azure.com/org/gocardless/_apis/git/repositories/bump" + end + + before do + stub_request(:get, repo_api_url). + to_return(status: 200, + body: fixture("azure", "bump_repo.json"), + headers: json_header) + stub_request( + :get, + "#{repo_api_url}/refs?filter=heads/#{CGI.escape(branch_name)}" + ).to_return( + status: 200, + body: fixture("azure", "branch_not_found.json"), + headers: json_header + ) + stub_request(:post, "#{repo_api_url}/pushes?api-version=5.0"). + to_return(status: 200, + headers: json_header) + stub_request(:post, "#{repo_api_url}/pullrequests?api-version=5.0"). + to_return(status: 200, + headers: json_header) + + # dependency lookups + stub_request( + :get, + "https://api.github.com/repos/gocardless/bump/labels?per_page=100" + ).to_return(status: 200, + body: fixture("gitlab", "labels_with_dependencies.json"), + headers: json_header) + end + + describe "#create" do + it "pushes a commit to GitLab and creates a pull request" do + creator.create + + expect(WebMock). + to have_requested(:post, "#{repo_api_url}/pushes?api-version=5.0") + expect(WebMock). + to have_requested(:post, "#{repo_api_url}/pullrequests?api-version=5.0") + end + + context "when the branch already exists" do + before do + stub_request( + :get, + "#{repo_api_url}/refs?filter=heads/#{CGI.escape(branch_name)}" + ).to_return( + status: 200, + body: fixture("azure", "default_branch.json"), + headers: json_header + ) + end + + context "but a pull request to this branch doesn't" do + before do + stub_request( + :get, + "#{repo_api_url}/pullrequests?" \ + "searchCriteria.sourceRefName=refs/heads/" + branch_name + + "&searchCriteria.status=all" \ + "&searchCriteria.targetRefName=refs/heads/master" + ).to_return( + status: 200, + body: fixture("azure", "branch_not_found.json"), + headers: json_header + ) + end + + context "and the commit doesn't already exists on that branch" do + before do + stub_request( + :get, + "#{repo_api_url}/commits?" \ + "searchCriteria.itemVersion.version=" + branch_name + ).to_return(status: 200, + body: fixture("azure", "commits_with_existing.json"), + headers: json_header) + end + + it "creates a commit and pull request with the right details" do + expect(creator.create).to_not be_nil + + expect(WebMock). + to have_requested( + :post, + "#{repo_api_url}/pushes?api-version=5.0" + ) + expect(WebMock). + to have_requested( + :post, + "#{repo_api_url}/pullrequests?api-version=5.0" + ) + end + end + + context "and a commit already exists on that branch" do + before do + stub_request( + :get, + "#{repo_api_url}/commits?" \ + "searchCriteria.itemVersion.version=" + branch_name + ).to_return( + status: 200, + body: fixture("azure", "commits_with_existing.json"), + headers: json_header + ) + end + + it "creates a pull request but not a commit" do + expect(creator.create).to_not be_nil + + expect(WebMock). + to have_requested( + :post, + "#{repo_api_url}/pushes?api-version=5.0" + ) + expect(WebMock). + to have_requested( + :post, + "#{repo_api_url}/pullrequests?api-version=5.0" + ) + end + end + end + + context "and a pull request to this branch already exists" do + before do + stub_request( + :get, + "#{repo_api_url}/commits?" \ + "searchCriteria.itemVersion.version=" + branch_name + ).to_return(status: 200, + body: fixture("azure", "commits_with_existing.json"), + headers: json_header) + + stub_request( + :get, + "#{repo_api_url}/pullrequests?searchCriteria.status=all" \ + "&searchCriteria.sourceRefName=refs/heads/" + branch_name + + "&searchCriteria.targetRefName=refs/heads/master" + ).to_return( + status: 200, + body: fixture("azure", "pull_request.json"), + headers: json_header + ) + end + + it "doesn't create a commit or pull request (and returns nil)" do + expect(creator.create).to be_nil + + expect(WebMock). + to_not have_requested( + :post, + "#{repo_api_url}/pushes?api-version=5.0" + ) + expect(WebMock). + to_not have_requested( + :post, + "#{repo_api_url}/pullrequests?api-version=5.0" + ) + end + end + end + end +end diff --git a/common/spec/dependabot/pull_request_creator_spec.rb b/common/spec/dependabot/pull_request_creator_spec.rb index 4e53703a97..644dd9b14c 100644 --- a/common/spec/dependabot/pull_request_creator_spec.rb +++ b/common/spec/dependabot/pull_request_creator_spec.rb @@ -155,5 +155,28 @@ creator.create end end + + context "with an Azure source" do + let(:source) { Dependabot::Source.new(provider: "azure", repo: "gc/bp") } + let(:dummy_creator) { instance_double(described_class::Azure) } + + it "delegates to PullRequestCreator::Azure with correct params" do + expect(described_class::Azure). + to receive(:new). + with( + source: source, + branch_name: "dependabot/bundler/business-1.5.0", + base_commit: base_commit, + credentials: credentials, + files: files, + commit_message: "Commit msg", + pr_description: "PR msg", + pr_name: "PR name", + labeler: instance_of(described_class::Labeler) + ).and_return(dummy_creator) + expect(dummy_creator).to receive(:create) + creator.create + end + end end end diff --git a/common/spec/fixtures/azure/branch_not_found.json b/common/spec/fixtures/azure/branch_not_found.json new file mode 100644 index 0000000000..8548e216bd --- /dev/null +++ b/common/spec/fixtures/azure/branch_not_found.json @@ -0,0 +1,4 @@ +{ + "value": [], + "count": 0 +} diff --git a/common/spec/fixtures/azure/bump_repo.json b/common/spec/fixtures/azure/bump_repo.json new file mode 100644 index 0000000000..2f5270f604 --- /dev/null +++ b/common/spec/fixtures/azure/bump_repo.json @@ -0,0 +1,49 @@ +{ + "id": "3c492e10-aa73-4855-b11e-5d6d9bd7d03a", + "name": "bump", + "url": "https://dev.azure.com/org/8929b42a-8f67-4075-bdb1-908ea8ebfb3a/_apis/git/repositories/3c492e10-aa73-4855-b11e-5d6d9bd7d03a", + "project": { + "id": "8929b42a-8f67-4075-bdb1-908ea8ebfb3a", + "name": "bump", + "description": "Example project", + "url": "https://dev.azure.com/org/_apis/projects/8929b42a-8f67-4075-bdb1-908ea8ebfb3a", + "state": "wellFormed", + "revision": 46, + "visibility": "private", + "lastUpdateTime": "2019-06-11T20:18:58.59Z" + }, + "defaultBranch": "refs/heads/master", + "size": 13541, + "remoteUrl": "https://org@dev.azure.com/org/gocardless/_git/bump", + "sshUrl": "git@ssh.dev.azure.com:v3/org/gocardless/bump", + "webUrl": "https://dev.azure.com/org/gocardless/_git/bump", + "_links": { + "self": { + "href": "https://dev.azure.com/org/8929b42a-8f67-4075-bdb1-908ea8ebfb3a/_apis/git/repositories/3c492e10-aa73-4855-b11e-5d6d9bd7d03a" + }, + "project": { + "href": "vstfs:///Classification/TeamProject/8929b42a-8f67-4075-bdb1-908ea8ebfb3a" + }, + "web": { + "href": "https://dev.azure.com/org/gocardless/_git/bump" + }, + "ssh": { + "href": "git@ssh.dev.azure.com:v3/org/gocardless/bump" + }, + "commits": { + "href": "https://dev.azure.com/org/8929b42a-8f67-4075-bdb1-908ea8ebfb3a/_apis/git/repositories/3c492e10-aa73-4855-b11e-5d6d9bd7d03a/commits" + }, + "refs": { + "href": "https://dev.azure.com/org/8929b42a-8f67-4075-bdb1-908ea8ebfb3a/_apis/git/repositories/3c492e10-aa73-4855-b11e-5d6d9bd7d03a/refs" + }, + "pullRequests": { + "href": "https://dev.azure.com/org/8929b42a-8f67-4075-bdb1-908ea8ebfb3a/_apis/git/repositories/3c492e10-aa73-4855-b11e-5d6d9bd7d03a/pullRequests" + }, + "items": { + "href": "https://dev.azure.com/org/8929b42a-8f67-4075-bdb1-908ea8ebfb3a/_apis/git/repositories/3c492e10-aa73-4855-b11e-5d6d9bd7d03a/items" + }, + "pushes": { + "href": "https://dev.azure.com/org/8929b42a-8f67-4075-bdb1-908ea8ebfb3a/_apis/git/repositories/3c492e10-aa73-4855-b11e-5d6d9bd7d03a/pushes" + } + } +} diff --git a/common/spec/fixtures/azure/business_files.json b/common/spec/fixtures/azure/business_files.json new file mode 100644 index 0000000000..31f81ef5b9 --- /dev/null +++ b/common/spec/fixtures/azure/business_files.json @@ -0,0 +1,37 @@ +{ + "objectId": "9fea8a9fd1877daecde8f80137f9dfee6ec0b01a", + "url": "https://dev.azure.com/org/gocardless/_apis/git/repositories/bump/trees/9fea8a9fd1877daecde8f80137f9dfee6ec0b01a", + "treeEntries": [ + { + "objectId": "3e759b75bf455ac809d0987d369aab89137b568a", + "relativePath": "requirements.txt", + "mode": "100644", + "gitObjectType": "blob", + "url": "https://dev.azure.com/org/8929b42a-8f67-4075-bdb1-908ea8ebfb3a/_apis/git/repositories/3c492e10-aa73-4855-b11e-5d6d9bd7d03a/blobs/3e759b75bf455ac809d0987d369aab89137b568a", + "size": 5582 + } + ], + "size": 180, + "_links": { + "self": { + "href": "https://dev.azure.com/org/gocardless/_apis/git/repositories/bump/trees/9fea8a9fd1877daecde8f80137f9dfee6ec0b01a" + }, + "repository": { + "href": "https://dev.azure.com/org/8929b42a-8f67-4075-bdb1-908ea8ebfb3a/_apis/git/repositories/3c492e10-aa73-4855-b11e-5d6d9bd7d03a" + }, + "treeEntries": [ + { + "href": "https://dev.azure.com/org/8929b42a-8f67-4075-bdb1-908ea8ebfb3a/_apis/git/repositories/3c492e10-aa73-4855-b11e-5d6d9bd7d03a/blobs/3e759b75bf455ac809d0987d369aab89137b568a" + }, + { + "href": "https://dev.azure.com/org/8929b42a-8f67-4075-bdb1-908ea8ebfb3a/_apis/git/repositories/3c492e10-aa73-4855-b11e-5d6d9bd7d03a/trees/8b23cf04122670142ba2e64c7b3293f82409726a" + }, + { + "href": "https://dev.azure.com/org/8929b42a-8f67-4075-bdb1-908ea8ebfb3a/_apis/git/repositories/3c492e10-aa73-4855-b11e-5d6d9bd7d03a/blobs/d476f1a7d1d6b24e05ca34abec0301c0a209a34a" + }, + { + "href": "https://dev.azure.com/org/8929b42a-8f67-4075-bdb1-908ea8ebfb3a/_apis/git/repositories/3c492e10-aa73-4855-b11e-5d6d9bd7d03a/blobs/925dd1b77d02eb1366af7b4b3f878099a7a407fa" + } + ] + } +} diff --git a/common/spec/fixtures/azure/business_folder.json b/common/spec/fixtures/azure/business_folder.json new file mode 100644 index 0000000000..ff9f9655d2 --- /dev/null +++ b/common/spec/fixtures/azure/business_folder.json @@ -0,0 +1,19 @@ +{ + "objectId": "9fea8a9fd1877daecde8f80137f9dfee6ec0b01a", + "gitObjectType": "tree", + "commitId": "9c8376e9b2e943c2c72fac4b239876f377f0305a", + "path": "/", + "isFolder": true, + "url": "https://dev.azure.com/org/8929b42a-8f67-4075-bdb1-908ea8ebfb3a/_apis/git/repositories/3c492e10-aa73-4855-b11e-5d6d9bd7d03a/items?path=%2F&versionType=Branch&versionOptions=None", + "_links": { + "self": { + "href": "https://dev.azure.com/org/8929b42a-8f67-4075-bdb1-908ea8ebfb3a/_apis/git/repositories/3c492e10-aa73-4855-b11e-5d6d9bd7d03a/items?path=%2F&versionType=Branch&versionOptions=None" + }, + "repository": { + "href": "https://dev.azure.com/org/8929b42a-8f67-4075-bdb1-908ea8ebfb3a/_apis/git/repositories/3c492e10-aa73-4855-b11e-5d6d9bd7d03a" + }, + "tree": { + "href": "https://dev.azure.com/org/8929b42a-8f67-4075-bdb1-908ea8ebfb3a/_apis/git/repositories/3c492e10-aa73-4855-b11e-5d6d9bd7d03a/trees/9fea8a9fd1877daecde8f80137f9dfee6ec0b01a" + } + } +} diff --git a/common/spec/fixtures/azure/commits_with_existing.json b/common/spec/fixtures/azure/commits_with_existing.json new file mode 100644 index 0000000000..6510a1bd2a --- /dev/null +++ b/common/spec/fixtures/azure/commits_with_existing.json @@ -0,0 +1,26 @@ +{ + "count": 1, + "value": [ + { + "commitId": "9c8376e9b2e943c2c72fac4b239876f377f0305a", + "author": { + "name": "Bob Jones", + "email": "Bob.Jones@org.com", + "date": "2019-06-11T20:21:23Z" + }, + "committer": { + "name": "Bob Jones", + "email": "Bob.Jones@org.com", + "date": "2019-06-11T20:21:23Z" + }, + "comment": "Added example file", + "changeCounts": { + "Add": 1, + "Edit": 0, + "Delete": 0 + }, + "url": "https://dev.azure.com/org/8929b42a-8f67-4075-bdb1-908ea8ebfb3a/_apis/git/repositories/3c492e10-aa73-4855-b11e-5d6d9bd7d03a/commits/9c8376e9b2e943c2c72fac4b239876f377f0305a", + "remoteUrl": "https://dev.azure.com/org/gocardless/_git/bump/commit/9c8376e9b2e943c2c72fac4b239876f377f0305a" + } + ] +} diff --git a/common/spec/fixtures/azure/default_branch.json b/common/spec/fixtures/azure/default_branch.json new file mode 100644 index 0000000000..e4265bd943 --- /dev/null +++ b/common/spec/fixtures/azure/default_branch.json @@ -0,0 +1,23 @@ +{ + "value": [ + { + "name": "refs/heads/master", + "objectId": "9c8376e9b2e943c2c72fac4b239876f377f0305a", + "creator": { + "displayName": "Bob Jones", + "url": "https://app.vssps.visualstudio.com/A3aa6c2ad-bb93-43c9-b957-ef1b14cf6ada/_apis/Identities/5ca0c171-a590-4ceb-a8d4-36c593907c1a", + "_links": { + "avatar": { + "href": "https://dev.azure.com/org/_apis/GraphProfile/MemberAvatars/aad.MTRjNTllMmQtODU2Yi03OTZlLThjZGQtZmU0ZTVhYWUyNGNa" + } + }, + "id": "5ca0c171-a590-4ceb-a8d4-36c593907c1a", + "uniqueName": "Bob.Jones@org.com", + "imageUrl": "https://dev.azure.com/org/_api/_common/identityImage?id=5ca0c171-a590-4ceb-a8d4-36c593907c1a", + "descriptor": "aad.MTRjNTllMmQtODU2Yi03OTZlLThjZGQtZmU0ZTVhYWUyNGNa" + }, + "url": "https://dev.azure.com/org/8929b42a-8f67-4075-bdb1-908ea8ebfb3a/_apis/git/repositories/3c492e10-aa73-4855-b11e-5d6d9bd7d03a/refs?filter=heads%2Fmaster" + } + ], + "count": 1 +} diff --git a/common/spec/fixtures/azure/gemspec_content b/common/spec/fixtures/azure/gemspec_content new file mode 100644 index 0000000000..7a92f54bbf --- /dev/null +++ b/common/spec/fixtures/azure/gemspec_content @@ -0,0 +1,61 @@ +# -*- encoding: utf-8 -*- +# stub: pg 1.2.0.pre20180828173948 ruby lib +# stub: ext/extconf.rb + +Gem::Specification.new do |s| + s.name = "pg".freeze + s.version = "1.2.0.pre20180828173948" + + s.required_rubygems_version = Gem::Requirement.new("> 1.3.1".freeze) if s.respond_to? :required_rubygems_version= + s.require_paths = ["lib".freeze] + s.authors = ["Michael Granger".freeze, "Lars Kanis".freeze] + s.cert_chain = ["certs/ged.pem".freeze] + s.date = "2018-08-29" + s.description = "Pg is the Ruby interface to the {PostgreSQL RDBMS}[http://www.postgresql.org/].\n\nIt works with {PostgreSQL 9.2 and later}[http://www.postgresql.org/support/versioning/].\n\nA small example usage:\n\n #!/usr/bin/env ruby\n\n require 'pg'\n\n # Output a table of current connections to the DB\n conn = PG.connect( dbname: 'sales' )\n conn.exec( \"SELECT * FROM pg_stat_activity\" ) do |result|\n puts \" PID | User | Query\"\n result.each do |row|\n puts \" %7d | %-16s | %s \" %\n row.values_at('procpid', 'usename', 'current_query')\n end\n end".freeze + s.email = ["ged@FaerieMUD.org".freeze, "lars@greiz-reinsdorf.de".freeze] + s.extensions = ["ext/extconf.rb".freeze] + s.extra_rdoc_files = ["Contributors.rdoc".freeze, "History.rdoc".freeze, "Manifest.txt".freeze, "README-OS_X.rdoc".freeze, "README-Windows.rdoc".freeze, "README.ja.rdoc".freeze, "README.rdoc".freeze, "ext/errorcodes.txt".freeze, "Contributors.rdoc".freeze, "History.rdoc".freeze, "README-OS_X.rdoc".freeze, "README-Windows.rdoc".freeze, "README.ja.rdoc".freeze, "README.rdoc".freeze, "POSTGRES".freeze, "LICENSE".freeze, "ext/gvl_wrappers.c".freeze, "ext/pg.c".freeze, "ext/pg_binary_decoder.c".freeze, "ext/pg_binary_encoder.c".freeze, "ext/pg_coder.c".freeze, "ext/pg_connection.c".freeze, "ext/pg_copy_coder.c".freeze, "ext/pg_errors.c".freeze, "ext/pg_result.c".freeze, "ext/pg_text_decoder.c".freeze, "ext/pg_text_encoder.c".freeze, "ext/pg_tuple.c".freeze, "ext/pg_type_map.c".freeze, "ext/pg_type_map_all_strings.c".freeze, "ext/pg_type_map_by_class.c".freeze, "ext/pg_type_map_by_column.c".freeze, "ext/pg_type_map_by_mri_type.c".freeze, "ext/pg_type_map_by_oid.c".freeze, "ext/pg_type_map_in_ruby.c".freeze, "ext/util.c".freeze] + s.files = ["BSDL".freeze, "ChangeLog".freeze, "Contributors.rdoc".freeze, "History.rdoc".freeze, "LICENSE".freeze, "Manifest.txt".freeze, "POSTGRES".freeze, "README-OS_X.rdoc".freeze, "README-Windows.rdoc".freeze, "README.ja.rdoc".freeze, "README.rdoc".freeze, "Rakefile".freeze, "Rakefile.cross".freeze, "ext/errorcodes.def".freeze, "ext/errorcodes.rb".freeze, "ext/errorcodes.txt".freeze, "ext/extconf.rb".freeze, "ext/gvl_wrappers.c".freeze, "ext/gvl_wrappers.h".freeze, "ext/pg.c".freeze, "ext/pg.h".freeze, "ext/pg_binary_decoder.c".freeze, "ext/pg_binary_encoder.c".freeze, "ext/pg_coder.c".freeze, "ext/pg_connection.c".freeze, "ext/pg_copy_coder.c".freeze, "ext/pg_errors.c".freeze, "ext/pg_result.c".freeze, "ext/pg_text_decoder.c".freeze, "ext/pg_text_encoder.c".freeze, "ext/pg_tuple.c".freeze, "ext/pg_type_map.c".freeze, "ext/pg_type_map_all_strings.c".freeze, "ext/pg_type_map_by_class.c".freeze, "ext/pg_type_map_by_column.c".freeze, "ext/pg_type_map_by_mri_type.c".freeze, "ext/pg_type_map_by_oid.c".freeze, "ext/pg_type_map_in_ruby.c".freeze, "ext/util.c".freeze, "ext/util.h".freeze, "ext/vc/pg.sln".freeze, "ext/vc/pg_18/pg.vcproj".freeze, "ext/vc/pg_19/pg_19.vcproj".freeze, "lib/pg.rb".freeze, "lib/pg/basic_type_mapping.rb".freeze, "lib/pg/binary_decoder.rb".freeze, "lib/pg/coder.rb".freeze, "lib/pg/connection.rb".freeze, "lib/pg/constants.rb".freeze, "lib/pg/exceptions.rb".freeze, "lib/pg/result.rb".freeze, "lib/pg/text_decoder.rb".freeze, "lib/pg/text_encoder.rb".freeze, "lib/pg/tuple.rb".freeze, "lib/pg/type_map_by_column.rb".freeze, "spec/data/expected_trace.out".freeze, "spec/data/random_binary_data".freeze, "spec/helpers.rb".freeze, "spec/pg/basic_type_mapping_spec.rb".freeze, "spec/pg/connection_spec.rb".freeze, "spec/pg/connection_sync_spec.rb".freeze, "spec/pg/result_spec.rb".freeze, "spec/pg/tuple_spec.rb".freeze, "spec/pg/type_map_by_class_spec.rb".freeze, "spec/pg/type_map_by_column_spec.rb".freeze, "spec/pg/type_map_by_mri_type_spec.rb".freeze, "spec/pg/type_map_by_oid_spec.rb".freeze, "spec/pg/type_map_in_ruby_spec.rb".freeze, "spec/pg/type_map_spec.rb".freeze, "spec/pg/type_spec.rb".freeze, "spec/pg_spec.rb".freeze] + s.homepage = "https://bitbucket.org/ged/ruby-pg".freeze + s.licenses = ["BSD-3-Clause".freeze] + s.rdoc_options = ["--main".freeze, "README.rdoc".freeze] + s.required_ruby_version = Gem::Requirement.new(">= 2.0.0".freeze) + s.rubygems_version = "2.7.6".freeze + s.summary = "Pg is the Ruby interface to the {PostgreSQL RDBMS}[http://www.postgresql.org/]".freeze + + if s.respond_to? :specification_version then + s.specification_version = 4 + + if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then + s.add_development_dependency(%q.freeze, ["~> 1.4"]) + s.add_development_dependency(%q.freeze, ["~> 0.9"]) + s.add_development_dependency(%q.freeze, ["~> 0.2"]) + s.add_development_dependency(%q.freeze, ["~> 1.0"]) + s.add_development_dependency(%q.freeze, [">= 0.6.2", "~> 0.6"]) + s.add_development_dependency(%q.freeze, ["~> 1.0"]) + s.add_development_dependency(%q.freeze, ["~> 3.5"]) + s.add_development_dependency(%q.freeze, ["~> 5.1"]) + s.add_development_dependency(%q.freeze, ["~> 3.16"]) + else + s.add_dependency(%q.freeze, ["~> 1.4"]) + s.add_dependency(%q.freeze, ["~> 0.9"]) + s.add_dependency(%q.freeze, ["~> 0.2"]) + s.add_dependency(%q.freeze, ["~> 1.0"]) + s.add_dependency(%q.freeze, [">= 0.6.2", "~> 0.6"]) + s.add_dependency(%q.freeze, ["~> 1.0"]) + s.add_dependency(%q.freeze, ["~> 3.5"]) + s.add_dependency(%q.freeze, ["~> 5.1"]) + s.add_dependency(%q.freeze, ["~> 3.16"]) + end + else + s.add_dependency(%q.freeze, ["~> 1.4"]) + s.add_dependency(%q.freeze, ["~> 0.9"]) + s.add_dependency(%q.freeze, ["~> 0.2"]) + s.add_dependency(%q.freeze, ["~> 1.0"]) + s.add_dependency(%q.freeze, [">= 0.6.2", "~> 0.6"]) + s.add_dependency(%q.freeze, ["~> 1.0"]) + s.add_dependency(%q.freeze, ["~> 3.5"]) + s.add_dependency(%q.freeze, ["~> 5.1"]) + s.add_dependency(%q.freeze, ["~> 3.16"]) + end +end diff --git a/common/spec/fixtures/azure/master_branch.json b/common/spec/fixtures/azure/master_branch.json new file mode 100644 index 0000000000..6efa983614 --- /dev/null +++ b/common/spec/fixtures/azure/master_branch.json @@ -0,0 +1,25 @@ +{ + "commit": { + "treeId": "9fea8a9fd1877daecde8f80137f9dfee6ec0b01a", + "commitId": "9c8376e9b2e943c2c72fac4b239876f377f0305a", + "author": { + "name": "Bob Jones", + "email": "Bob.Jones@org.com", + "date": "2019-06-11T20:21:23Z" + }, + "committer": { + "name": "Bob Jones", + "email": "Bob.Jones@org.com", + "date": "2019-06-11T20:21:23Z" + }, + "comment": "Added example file", + "parents": [ + "aa6251347fad80f67dfcbff32dd3c0ae1bc926ca" + ], + "url": "https://dev.azure.com/org/8929b42a-8f67-4075-bdb1-908ea8ebfb3a/_apis/git/repositories/3c492e10-aa73-4855-b11e-5d6d9bd7d03a/commits/9c8376e9b2e943c2c72fac4b239876f377f0305a" + }, + "name": "master", + "aheadCount": 0, + "behindCount": 0, + "isBaseVersion": true +} diff --git a/common/spec/fixtures/azure/no_files.json b/common/spec/fixtures/azure/no_files.json new file mode 100644 index 0000000000..6aa1d8e995 --- /dev/null +++ b/common/spec/fixtures/azure/no_files.json @@ -0,0 +1,29 @@ +{ + "objectId": "9fea8a9fd1877daecde8f80137f9dfee6ec0b01a", + "url": "https://dev.azure.com/org/gocardless/_apis/git/repositories/bump/trees/9fea8a9fd1877daecde8f80137f9dfee6ec0b01a", + "treeEntries": [ + ], + "size": 180, + "_links": { + "self": { + "href": "https://dev.azure.com/org/gocardless/_apis/git/repositories/bump/trees/9fea8a9fd1877daecde8f80137f9dfee6ec0b01a" + }, + "repository": { + "href": "https://dev.azure.com/org/8929b42a-8f67-4075-bdb1-908ea8ebfb3a/_apis/git/repositories/3c492e10-aa73-4855-b11e-5d6d9bd7d03a" + }, + "treeEntries": [ + { + "href": "https://dev.azure.com/org/8929b42a-8f67-4075-bdb1-908ea8ebfb3a/_apis/git/repositories/3c492e10-aa73-4855-b11e-5d6d9bd7d03a/blobs/3e759b75bf455ac809d0987d369aab89137b568a" + }, + { + "href": "https://dev.azure.com/org/8929b42a-8f67-4075-bdb1-908ea8ebfb3a/_apis/git/repositories/3c492e10-aa73-4855-b11e-5d6d9bd7d03a/trees/8b23cf04122670142ba2e64c7b3293f82409726a" + }, + { + "href": "https://dev.azure.com/org/8929b42a-8f67-4075-bdb1-908ea8ebfb3a/_apis/git/repositories/3c492e10-aa73-4855-b11e-5d6d9bd7d03a/blobs/d476f1a7d1d6b24e05ca34abec0301c0a209a34a" + }, + { + "href": "https://dev.azure.com/org/8929b42a-8f67-4075-bdb1-908ea8ebfb3a/_apis/git/repositories/3c492e10-aa73-4855-b11e-5d6d9bd7d03a/blobs/925dd1b77d02eb1366af7b4b3f878099a7a407fa" + } + ] + } +} diff --git a/common/spec/fixtures/azure/other_branch.json b/common/spec/fixtures/azure/other_branch.json new file mode 100644 index 0000000000..e26c373aa4 --- /dev/null +++ b/common/spec/fixtures/azure/other_branch.json @@ -0,0 +1,25 @@ +{ + "commit": { + "treeId": "9fea8a9fd1877daecde8f80137f9dfee6ec0b01a", + "commitId": "8c8376e9b2e943c2c72fac4b239876f377f0305b", + "author": { + "name": "Bob Jones", + "email": "Bob.Jones@org.com", + "date": "2019-06-11T20:21:23Z" + }, + "committer": { + "name": "Bob Jones", + "email": "Bob.Jones@org.com", + "date": "2019-06-11T20:21:23Z" + }, + "comment": "Added example file", + "parents": [ + "aa6251347fad80f67dfcbff32dd3c0ae1bc926ca" + ], + "url": "https://dev.azure.com/org/8929b42a-8f67-4075-bdb1-908ea8ebfb3a/_apis/git/repositories/3c492e10-aa73-4855-b11e-5d6d9bd7d03a/commits/9c8376e9b2e943c2c72fac4b239876f377f0305a" + }, + "name": "master", + "aheadCount": 0, + "behindCount": 0, + "isBaseVersion": true +} diff --git a/common/spec/fixtures/azure/pull_request.json b/common/spec/fixtures/azure/pull_request.json new file mode 100644 index 0000000000..b861ffc032 --- /dev/null +++ b/common/spec/fixtures/azure/pull_request.json @@ -0,0 +1,10 @@ +{ + "value": [ + { + "pullRequestId": 3, + "codeReviewId": 3, + "status": "active" + } + ], + "count": 1 +}