| | |
| | |
| |
|
| | require "sorbet-runtime" |
| | require "dependabot/metadata_finders" |
| | require "dependabot/credential" |
| |
|
| | module Dependabot |
| | class PullRequestCreator |
| | extend T::Sig |
| |
|
| | require "dependabot/pull_request_creator/azure" |
| | require "dependabot/pull_request_creator/bitbucket" |
| | require "dependabot/pull_request_creator/codecommit" |
| | require "dependabot/pull_request_creator/github" |
| | require "dependabot/pull_request_creator/gitlab" |
| | require "dependabot/pull_request_creator/message_builder" |
| | require "dependabot/pull_request_creator/branch_namer" |
| | require "dependabot/pull_request_creator/labeler" |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | DEFAULT_GITHUB_REDIRECTION_SERVICE = "redirect.github.com" |
| |
|
| | class RepoNotFound < StandardError; end |
| |
|
| | class RepoArchived < StandardError; end |
| |
|
| | class RepoDisabled < StandardError; end |
| |
|
| | class NoHistoryInCommon < StandardError; end |
| |
|
| | class UnmergedPRExists < StandardError; end |
| |
|
| | class BranchAlreadyExists < StandardError; end |
| |
|
| | class BaseCommitNotUpToDate < StandardError; end |
| |
|
| | class UnexpectedError < StandardError; end |
| |
|
| | |
| | class AnnotationError < StandardError |
| | extend T::Sig |
| |
|
| | sig { returns(StandardError) } |
| | attr_reader :cause |
| |
|
| | |
| | |
| | |
| | sig { returns(Sawyer::Resource) } |
| | attr_reader :pull_request |
| |
|
| | sig { params(cause: StandardError, pull_request: Sawyer::Resource).void } |
| | def initialize(cause, pull_request) |
| | super(cause.message) |
| | @cause = cause |
| | @pull_request = pull_request |
| | end |
| | end |
| |
|
| | sig { returns(Dependabot::Source) } |
| | attr_reader :source |
| |
|
| | sig { returns(T::Array[Dependabot::Dependency]) } |
| | attr_reader :dependencies |
| |
|
| | sig { returns(T::Array[Dependabot::DependencyFile]) } |
| | attr_reader :files |
| |
|
| | sig { returns(String) } |
| | attr_reader :base_commit |
| |
|
| | sig { returns(T::Array[Dependabot::Credential]) } |
| | attr_reader :credentials |
| |
|
| | sig { returns(T.nilable(String)) } |
| | attr_reader :pr_message_header |
| |
|
| | sig { returns(T.nilable(String)) } |
| | attr_reader :pr_message_footer |
| |
|
| | sig { returns(T.nilable(T::Array[String])) } |
| | attr_reader :custom_labels |
| |
|
| | sig { returns(T.nilable(T::Hash[Symbol, String])) } |
| | attr_reader :author_details |
| |
|
| | sig { returns(T.nilable(String)) } |
| | attr_reader :signature_key |
| |
|
| | sig { returns(T::Hash[Symbol, T.untyped]) } |
| | attr_reader :commit_message_options |
| |
|
| | sig { returns(T::Hash[String, String]) } |
| | attr_reader :vulnerabilities_fixed |
| |
|
| | AzureReviewers = T.type_alias { T.nilable(T::Array[String]) } |
| | GithubReviewers = T.type_alias { T.nilable(T::Hash[String, T::Array[String]]) } |
| | GitLabReviewers = T.type_alias { T.nilable(T::Hash[Symbol, T::Array[Integer]]) } |
| | Reviewers = T.type_alias { T.any(AzureReviewers, GithubReviewers, GitLabReviewers) } |
| |
|
| | sig { returns(Reviewers) } |
| | attr_reader :reviewers |
| |
|
| | sig { returns(T.nilable(T.any(T::Array[String], T::Array[Integer]))) } |
| | attr_reader :assignees |
| |
|
| | sig { returns(T.nilable(T.any(T::Array[String], Integer))) } |
| | attr_reader :milestone |
| |
|
| | sig { returns(String) } |
| | attr_reader :branch_name_separator |
| |
|
| | sig { returns(String) } |
| | attr_reader :branch_name_prefix |
| |
|
| | sig { returns(T.nilable(Integer)) } |
| | attr_reader :branch_name_max_length |
| |
|
| | sig { returns(String) } |
| | attr_reader :github_redirection_service |
| |
|
| | sig { returns(T.nilable(T::Hash[String, String])) } |
| | attr_reader :custom_headers |
| |
|
| | sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) } |
| | attr_reader :provider_metadata |
| |
|
| | sig { returns(T.nilable(Dependabot::DependencyGroup)) } |
| | attr_reader :dependency_group |
| |
|
| | sig { returns(T.nilable(Integer)) } |
| | attr_reader :pr_message_max_length |
| |
|
| | sig { returns(T.nilable(Encoding)) } |
| | attr_reader :pr_message_encoding |
| |
|
| | sig do |
| | params( |
| | source: Dependabot::Source, |
| | base_commit: String, |
| | dependencies: T::Array[Dependabot::Dependency], |
| | files: T::Array[Dependabot::DependencyFile], |
| | credentials: T::Array[Dependabot::Credential], |
| | pr_message_header: T.nilable(String), |
| | pr_message_footer: T.nilable(String), |
| | custom_labels: T.nilable(T::Array[String]), |
| | author_details: T.nilable(T::Hash[Symbol, String]), |
| | signature_key: T.nilable(String), |
| | commit_message_options: T::Hash[Symbol, T.untyped], |
| | vulnerabilities_fixed: T::Hash[String, String], |
| | reviewers: Reviewers, |
| | assignees: T.nilable(T.any(T::Array[String], T::Array[Integer])), |
| | milestone: T.nilable(T.any(T::Array[String], Integer)), |
| | branch_name_separator: String, |
| | branch_name_prefix: String, |
| | branch_name_max_length: T.nilable(Integer), |
| | label_language: T::Boolean, |
| | automerge_candidate: T::Boolean, |
| | github_redirection_service: String, |
| | custom_headers: T.nilable(T::Hash[String, String]), |
| | require_up_to_date_base: T::Boolean, |
| | provider_metadata: T.nilable(T::Hash[Symbol, T.untyped]), |
| | message: T.nilable( |
| | T.any(Dependabot::PullRequestCreator::Message, Dependabot::PullRequestCreator::MessageBuilder) |
| | ), |
| | dependency_group: T.nilable(Dependabot::DependencyGroup), |
| | pr_message_max_length: T.nilable(Integer), |
| | pr_message_encoding: T.nilable(Encoding) |
| | ) |
| | .void |
| | end |
| | def initialize( |
| | source:, |
| | base_commit:, |
| | dependencies:, |
| | files:, |
| | credentials:, |
| | pr_message_header: nil, |
| | pr_message_footer: nil, |
| | custom_labels: nil, |
| | author_details: nil, |
| | signature_key: nil, |
| | commit_message_options: {}, |
| | vulnerabilities_fixed: {}, |
| | reviewers: nil, |
| | assignees: nil, |
| | milestone: nil, |
| | branch_name_separator: "/", |
| | branch_name_prefix: "dependabot", |
| | branch_name_max_length: nil, |
| | label_language: false, |
| | automerge_candidate: false, |
| | github_redirection_service: DEFAULT_GITHUB_REDIRECTION_SERVICE, |
| | custom_headers: nil, |
| | require_up_to_date_base: false, |
| | provider_metadata: {}, |
| | message: nil, |
| | dependency_group: nil, |
| | pr_message_max_length: nil, |
| | pr_message_encoding: nil |
| | ) |
| | @dependencies = dependencies |
| | @source = source |
| | @base_commit = base_commit |
| | @files = files |
| | @credentials = credentials |
| | @pr_message_header = pr_message_header |
| | @pr_message_footer = pr_message_footer |
| | @author_details = author_details |
| | @signature_key = signature_key |
| | @commit_message_options = commit_message_options |
| | @custom_labels = custom_labels |
| | @reviewers = reviewers |
| | @assignees = assignees |
| | @milestone = milestone |
| | @vulnerabilities_fixed = vulnerabilities_fixed |
| | @branch_name_separator = branch_name_separator |
| | @branch_name_prefix = branch_name_prefix |
| | @branch_name_max_length = branch_name_max_length |
| | @label_language = label_language |
| | @automerge_candidate = automerge_candidate |
| | @github_redirection_service = github_redirection_service |
| | @custom_headers = custom_headers |
| | @require_up_to_date_base = require_up_to_date_base |
| | @provider_metadata = provider_metadata |
| | @message = message |
| | @dependency_group = dependency_group |
| | @pr_message_max_length = pr_message_max_length |
| | @pr_message_encoding = pr_message_encoding |
| |
|
| | check_dependencies_have_previous_version |
| | end |
| |
|
| | sig { void } |
| | def check_dependencies_have_previous_version |
| | return if dependencies.all? { |d| requirements_changed?(d) } |
| | return if dependencies.all?(&:previous_version) |
| |
|
| | raise "Dependencies must have a previous version or changed " \ |
| | "requirement to have a pull request created for them!" |
| | end |
| |
|
| | |
| | |
| | |
| | sig { returns(T.untyped) } |
| | def create |
| | case source.provider |
| | when "github" then github_creator.create |
| | when "gitlab" then gitlab_creator.create |
| | when "azure" then azure_creator.create |
| | when "bitbucket" then bitbucket_creator.create |
| | when "codecommit" then codecommit_creator.create |
| | else raise "Unsupported provider #{source.provider}" |
| | end |
| | end |
| |
|
| | private |
| |
|
| | sig { returns(T::Boolean) } |
| | def label_language? |
| | @label_language |
| | end |
| |
|
| | sig { returns(T::Boolean) } |
| | def automerge_candidate? |
| | @automerge_candidate |
| | end |
| |
|
| | sig { returns(T::Boolean) } |
| | def require_up_to_date_base? |
| | @require_up_to_date_base |
| | end |
| |
|
| | sig { returns(Dependabot::PullRequestCreator::Github) } |
| | def github_creator |
| | Github.new( |
| | source: source, |
| | branch_name: branch_namer.new_branch_name, |
| | base_commit: base_commit, |
| | credentials: credentials, |
| | files: files, |
| | commit_message: T.must(message.commit_message), |
| | pr_description: T.must(message.pr_message), |
| | pr_name: T.must(message.pr_name), |
| | author_details: author_details, |
| | signature_key: signature_key, |
| | labeler: labeler, |
| | reviewers: T.cast(reviewers, GithubReviewers), |
| | assignees: T.cast(assignees, T.nilable(T::Array[String])), |
| | milestone: T.cast(milestone, T.nilable(Integer)), |
| | custom_headers: custom_headers, |
| | require_up_to_date_base: require_up_to_date_base? |
| | ) |
| | end |
| |
|
| | sig { returns(Dependabot::PullRequestCreator::Gitlab) } |
| | def gitlab_creator |
| | Gitlab.new( |
| | source: source, |
| | branch_name: branch_namer.new_branch_name, |
| | base_commit: base_commit, |
| | credentials: credentials, |
| | files: files, |
| | commit_message: T.must(message.commit_message), |
| | pr_description: T.must(message.pr_message), |
| | pr_name: T.must(message.pr_name), |
| | author_details: author_details, |
| | labeler: labeler, |
| | approvers: T.cast(reviewers, T.nilable(T::Hash[Symbol, T::Array[Integer]])), |
| | assignees: T.cast(assignees, T.nilable(T::Array[Integer])), |
| | milestone: milestone, |
| | target_project_id: T.cast(provider_metadata&.fetch(:target_project_id, nil), T.nilable(Integer)) |
| | ) |
| | end |
| |
|
| | sig { returns(Dependabot::PullRequestCreator::Azure) } |
| | def azure_creator |
| | Azure.new( |
| | source: source, |
| | branch_name: branch_namer.new_branch_name, |
| | base_commit: base_commit, |
| | credentials: credentials, |
| | files: files, |
| | commit_message: T.must(message.commit_message), |
| | pr_description: T.must(message.pr_message), |
| | pr_name: T.must(message.pr_name), |
| | author_details: author_details, |
| | labeler: labeler, |
| | reviewers: T.cast(reviewers, AzureReviewers), |
| | assignees: T.cast(assignees, T.nilable(T::Array[String])), |
| | work_item: T.cast(provider_metadata&.fetch(:work_item, nil), T.nilable(Integer)) |
| | ) |
| | end |
| |
|
| | sig { returns(Dependabot::PullRequestCreator::Bitbucket) } |
| | def bitbucket_creator |
| | Bitbucket.new( |
| | source: source, |
| | branch_name: branch_namer.new_branch_name, |
| | base_commit: base_commit, |
| | credentials: credentials, |
| | files: files, |
| | commit_message: T.must(message.commit_message), |
| | pr_description: T.must(message.pr_message), |
| | pr_name: T.must(message.pr_name), |
| | author_details: author_details, |
| | labeler: nil, |
| | work_item: T.cast(provider_metadata&.fetch(:work_item, nil), T.nilable(Integer)) |
| | ) |
| | end |
| |
|
| | sig { returns(Dependabot::PullRequestCreator::Codecommit) } |
| | def codecommit_creator |
| | Codecommit.new( |
| | source: source, |
| | branch_name: branch_namer.new_branch_name, |
| | base_commit: base_commit, |
| | credentials: credentials, |
| | files: files, |
| | commit_message: T.must(message.commit_message), |
| | pr_description: T.must(message.pr_message), |
| | pr_name: T.must(message.pr_name), |
| | author_details: author_details, |
| | labeler: labeler, |
| | require_up_to_date_base: require_up_to_date_base? |
| | ) |
| | end |
| |
|
| | sig { returns(T.any(Dependabot::PullRequestCreator::Message, Dependabot::PullRequestCreator::MessageBuilder)) } |
| | def message |
| | return @message unless @message.nil? |
| |
|
| | case source.provider |
| | when "github" |
| | @pr_message_max_length = Github::PR_DESCRIPTION_MAX_LENGTH if @pr_message_max_length.nil? |
| | when "azure" |
| | @pr_message_max_length = Azure::PR_DESCRIPTION_MAX_LENGTH if @pr_message_max_length.nil? |
| | @pr_message_encoding = Azure::PR_DESCRIPTION_ENCODING if @pr_message_encoding.nil? |
| | when "codecommit" |
| | @pr_message_max_length = Codecommit::PR_DESCRIPTION_MAX_LENGTH if @pr_message_max_length.nil? |
| | when "bitbucket" |
| | @pr_message_max_length = Bitbucket::PR_DESCRIPTION_MAX_LENGTH if @pr_message_max_length.nil? |
| | end |
| |
|
| | @message = MessageBuilder.new( |
| | source: source, |
| | dependencies: dependencies, |
| | files: files, |
| | credentials: credentials, |
| | commit_message_options: commit_message_options, |
| | pr_message_header: pr_message_header, |
| | pr_message_footer: pr_message_footer, |
| | vulnerabilities_fixed: vulnerabilities_fixed, |
| | github_redirection_service: github_redirection_service, |
| | dependency_group: dependency_group, |
| | pr_message_max_length: pr_message_max_length, |
| | pr_message_encoding: pr_message_encoding |
| | ) |
| | end |
| |
|
| | sig { returns(Dependabot::PullRequestCreator::BranchNamer) } |
| | def branch_namer |
| | @branch_namer ||= T.let( |
| | BranchNamer.new( |
| | dependencies: dependencies, |
| | files: files, |
| | target_branch: source.branch, |
| | dependency_group: dependency_group, |
| | separator: branch_name_separator, |
| | prefix: branch_name_prefix, |
| | max_length: branch_name_max_length, |
| | includes_security_fixes: includes_security_fixes? |
| | ), |
| | T.nilable(Dependabot::PullRequestCreator::BranchNamer) |
| | ) |
| | end |
| |
|
| | sig { returns(Dependabot::PullRequestCreator::Labeler) } |
| | def labeler |
| | @labeler ||= T.let( |
| | Labeler.new( |
| | source: source, |
| | custom_labels: custom_labels, |
| | credentials: credentials, |
| | includes_security_fixes: includes_security_fixes?, |
| | dependencies: dependencies, |
| | label_language: label_language?, |
| | automerge_candidate: automerge_candidate? |
| | ), |
| | T.nilable(Dependabot::PullRequestCreator::Labeler) |
| | ) |
| | end |
| |
|
| | sig { returns(T::Boolean) } |
| | def includes_security_fixes? |
| | vulnerabilities_fixed.values.flatten.any? |
| | end |
| |
|
| | sig { params(dependency: Dependabot::Dependency).returns(T::Boolean) } |
| | def requirements_changed?(dependency) |
| | (dependency.requirements - T.must(dependency.previous_requirements)).any? |
| | end |
| | end |
| | end |
| |
|