dependabot-common 0.95.1 → 0.95.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/dependabot.rb +4 -0
- data/lib/dependabot/clients/bitbucket.rb +105 -0
- data/lib/dependabot/clients/github_with_retries.rb +121 -0
- data/lib/dependabot/clients/gitlab.rb +72 -0
- data/lib/dependabot/dependency.rb +115 -0
- data/lib/dependabot/dependency_file.rb +60 -0
- data/lib/dependabot/errors.rb +179 -0
- data/lib/dependabot/file_fetchers.rb +18 -0
- data/lib/dependabot/file_fetchers/README.md +65 -0
- data/lib/dependabot/file_fetchers/base.rb +368 -0
- data/lib/dependabot/file_parsers.rb +18 -0
- data/lib/dependabot/file_parsers/README.md +45 -0
- data/lib/dependabot/file_parsers/base.rb +31 -0
- data/lib/dependabot/file_parsers/base/dependency_set.rb +77 -0
- data/lib/dependabot/file_updaters.rb +18 -0
- data/lib/dependabot/file_updaters/README.md +58 -0
- data/lib/dependabot/file_updaters/base.rb +52 -0
- data/lib/dependabot/git_commit_checker.rb +412 -0
- data/lib/dependabot/metadata_finders.rb +18 -0
- data/lib/dependabot/metadata_finders/README.md +53 -0
- data/lib/dependabot/metadata_finders/base.rb +117 -0
- data/lib/dependabot/metadata_finders/base/changelog_finder.rb +321 -0
- data/lib/dependabot/metadata_finders/base/changelog_pruner.rb +177 -0
- data/lib/dependabot/metadata_finders/base/commits_finder.rb +221 -0
- data/lib/dependabot/metadata_finders/base/release_finder.rb +255 -0
- data/lib/dependabot/pull_request_creator.rb +155 -0
- data/lib/dependabot/pull_request_creator/branch_namer.rb +170 -0
- data/lib/dependabot/pull_request_creator/commit_signer.rb +63 -0
- data/lib/dependabot/pull_request_creator/github.rb +277 -0
- data/lib/dependabot/pull_request_creator/gitlab.rb +162 -0
- data/lib/dependabot/pull_request_creator/labeler.rb +373 -0
- data/lib/dependabot/pull_request_creator/message_builder.rb +906 -0
- data/lib/dependabot/pull_request_updater.rb +43 -0
- data/lib/dependabot/pull_request_updater/github.rb +165 -0
- data/lib/dependabot/shared_helpers.rb +224 -0
- data/lib/dependabot/source.rb +120 -0
- data/lib/dependabot/update_checkers.rb +18 -0
- data/lib/dependabot/update_checkers/README.md +67 -0
- data/lib/dependabot/update_checkers/base.rb +220 -0
- data/lib/dependabot/utils.rb +33 -0
- data/lib/dependabot/version.rb +5 -0
- data/lib/rubygems_version_patch.rb +14 -0
- metadata +44 -2
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dependabot/metadata_finders"
|
|
4
|
+
|
|
5
|
+
module Dependabot
|
|
6
|
+
class PullRequestCreator
|
|
7
|
+
require "dependabot/pull_request_creator/github"
|
|
8
|
+
require "dependabot/pull_request_creator/gitlab"
|
|
9
|
+
require "dependabot/pull_request_creator/message_builder"
|
|
10
|
+
require "dependabot/pull_request_creator/branch_namer"
|
|
11
|
+
require "dependabot/pull_request_creator/labeler"
|
|
12
|
+
|
|
13
|
+
class RepoNotFound < StandardError; end
|
|
14
|
+
class RepoArchived < StandardError; end
|
|
15
|
+
class NoHistoryInCommon < StandardError; end
|
|
16
|
+
|
|
17
|
+
attr_reader :source, :dependencies, :files, :base_commit,
|
|
18
|
+
:credentials, :pr_message_footer, :custom_labels,
|
|
19
|
+
:author_details, :signature_key, :vulnerabilities_fixed,
|
|
20
|
+
:reviewers, :assignees, :milestone, :branch_name_separator
|
|
21
|
+
|
|
22
|
+
def initialize(source:, base_commit:, dependencies:, files:, credentials:,
|
|
23
|
+
pr_message_footer: nil, custom_labels: nil,
|
|
24
|
+
author_details: nil, signature_key: nil,
|
|
25
|
+
reviewers: nil, assignees: nil, milestone: nil,
|
|
26
|
+
vulnerabilities_fixed: {}, branch_name_separator: "/",
|
|
27
|
+
label_language: false)
|
|
28
|
+
@dependencies = dependencies
|
|
29
|
+
@source = source
|
|
30
|
+
@base_commit = base_commit
|
|
31
|
+
@files = files
|
|
32
|
+
@credentials = credentials
|
|
33
|
+
@pr_message_footer = pr_message_footer
|
|
34
|
+
@author_details = author_details
|
|
35
|
+
@signature_key = signature_key
|
|
36
|
+
@custom_labels = custom_labels
|
|
37
|
+
@reviewers = reviewers
|
|
38
|
+
@assignees = assignees
|
|
39
|
+
@milestone = milestone
|
|
40
|
+
@vulnerabilities_fixed = vulnerabilities_fixed
|
|
41
|
+
@branch_name_separator = branch_name_separator
|
|
42
|
+
@label_language = label_language
|
|
43
|
+
|
|
44
|
+
check_dependencies_have_previous_version
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def check_dependencies_have_previous_version
|
|
48
|
+
return if library? && dependencies.all? { |d| requirements_changed?(d) }
|
|
49
|
+
return if dependencies.all?(&:previous_version)
|
|
50
|
+
|
|
51
|
+
raise "Dependencies must have a previous version or changed " \
|
|
52
|
+
"requirement to have a pull request created for them!"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def create
|
|
56
|
+
case source.provider
|
|
57
|
+
when "github" then github_creator.create
|
|
58
|
+
when "gitlab" then gitlab_creator.create
|
|
59
|
+
else raise "Unsupported provider #{source.provider}"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def label_language?
|
|
66
|
+
@label_language
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def github_creator
|
|
70
|
+
Github.new(
|
|
71
|
+
source: source,
|
|
72
|
+
branch_name: branch_namer.new_branch_name,
|
|
73
|
+
base_commit: base_commit,
|
|
74
|
+
credentials: credentials,
|
|
75
|
+
files: files,
|
|
76
|
+
commit_message: message_builder.commit_message,
|
|
77
|
+
pr_description: message_builder.pr_message,
|
|
78
|
+
pr_name: message_builder.pr_name,
|
|
79
|
+
author_details: author_details,
|
|
80
|
+
signature_key: signature_key,
|
|
81
|
+
labeler: labeler,
|
|
82
|
+
reviewers: reviewers,
|
|
83
|
+
assignees: assignees,
|
|
84
|
+
milestone: milestone
|
|
85
|
+
)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def gitlab_creator
|
|
89
|
+
Gitlab.new(
|
|
90
|
+
source: source,
|
|
91
|
+
branch_name: branch_namer.new_branch_name,
|
|
92
|
+
base_commit: base_commit,
|
|
93
|
+
credentials: credentials,
|
|
94
|
+
files: files,
|
|
95
|
+
commit_message: message_builder.commit_message,
|
|
96
|
+
pr_description: message_builder.pr_message,
|
|
97
|
+
pr_name: message_builder.pr_name,
|
|
98
|
+
author_details: author_details,
|
|
99
|
+
labeler: labeler,
|
|
100
|
+
approvers: reviewers,
|
|
101
|
+
assignee: assignees&.first,
|
|
102
|
+
milestone: milestone
|
|
103
|
+
)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def message_builder
|
|
107
|
+
@message_builder ||
|
|
108
|
+
MessageBuilder.new(
|
|
109
|
+
source: source,
|
|
110
|
+
dependencies: dependencies,
|
|
111
|
+
files: files,
|
|
112
|
+
credentials: credentials,
|
|
113
|
+
author_details: author_details,
|
|
114
|
+
pr_message_footer: pr_message_footer,
|
|
115
|
+
vulnerabilities_fixed: vulnerabilities_fixed
|
|
116
|
+
)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def branch_namer
|
|
120
|
+
@branch_namer ||=
|
|
121
|
+
BranchNamer.new(
|
|
122
|
+
dependencies: dependencies,
|
|
123
|
+
files: files,
|
|
124
|
+
target_branch: source.branch,
|
|
125
|
+
separator: branch_name_separator
|
|
126
|
+
)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def labeler
|
|
130
|
+
@labeler ||=
|
|
131
|
+
Labeler.new(
|
|
132
|
+
source: source,
|
|
133
|
+
custom_labels: custom_labels,
|
|
134
|
+
credentials: credentials,
|
|
135
|
+
includes_security_fixes: includes_security_fixes?,
|
|
136
|
+
dependencies: dependencies,
|
|
137
|
+
label_language: label_language?
|
|
138
|
+
)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def library?
|
|
142
|
+
return true if files.any? { |file| file.name.end_with?(".gemspec") }
|
|
143
|
+
|
|
144
|
+
dependencies.none?(&:appears_in_lockfile?)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def includes_security_fixes?
|
|
148
|
+
vulnerabilities_fixed.values.flatten.any?
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def requirements_changed?(dependency)
|
|
152
|
+
(dependency.requirements - dependency.previous_requirements).any?
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dependabot/metadata_finders"
|
|
4
|
+
require "dependabot/pull_request_creator"
|
|
5
|
+
|
|
6
|
+
module Dependabot
|
|
7
|
+
class PullRequestCreator
|
|
8
|
+
class BranchNamer
|
|
9
|
+
attr_reader :dependencies, :files, :target_branch, :separator
|
|
10
|
+
|
|
11
|
+
def initialize(dependencies:, files:, target_branch:, separator: "/")
|
|
12
|
+
@dependencies = dependencies
|
|
13
|
+
@files = files
|
|
14
|
+
@target_branch = target_branch
|
|
15
|
+
@separator = separator
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# rubocop:disable Metrics/AbcSize
|
|
19
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
|
20
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
|
21
|
+
def new_branch_name
|
|
22
|
+
@name ||=
|
|
23
|
+
if dependencies.count > 1 && updating_a_property?
|
|
24
|
+
property_name
|
|
25
|
+
elsif dependencies.count > 1 && updating_a_dependency_set?
|
|
26
|
+
dependency_set.fetch(:group)
|
|
27
|
+
elsif dependencies.count > 1
|
|
28
|
+
dependencies.map(&:name).join("-and-").tr(":", "-")
|
|
29
|
+
elsif library? && ref_changed?(dependencies.first)
|
|
30
|
+
dep = dependencies.first
|
|
31
|
+
"#{dep.name.tr(':', '-')}-#{new_ref(dep)}"
|
|
32
|
+
elsif library?
|
|
33
|
+
dep = dependencies.first
|
|
34
|
+
"#{dep.name.tr(':', '-')}-#{sanitized_requirement(dep)}"
|
|
35
|
+
else
|
|
36
|
+
dep = dependencies.first
|
|
37
|
+
"#{dep.name.tr(':', '-')}-#{new_version(dep)}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
branch_name = File.join(prefixes, @name).gsub(%r{/\.}, "/dot-")
|
|
41
|
+
|
|
42
|
+
# Some users need branch names without slashes
|
|
43
|
+
branch_name.gsub("/", separator)
|
|
44
|
+
end
|
|
45
|
+
# rubocop:enable Metrics/AbcSize
|
|
46
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
|
47
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def prefixes
|
|
52
|
+
[
|
|
53
|
+
"dependabot",
|
|
54
|
+
package_manager,
|
|
55
|
+
files.first.directory.tr(" ", "-"),
|
|
56
|
+
target_branch
|
|
57
|
+
].compact
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def package_manager
|
|
61
|
+
dependencies.first.package_manager
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def updating_a_property?
|
|
65
|
+
dependencies.first.
|
|
66
|
+
requirements.
|
|
67
|
+
any? { |r| r.dig(:metadata, :property_name) }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def updating_a_dependency_set?
|
|
71
|
+
dependencies.first.
|
|
72
|
+
requirements.
|
|
73
|
+
any? { |r| r.dig(:metadata, :dependency_set) }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def property_name
|
|
77
|
+
@property_name ||= dependencies.first.requirements.
|
|
78
|
+
find { |r| r.dig(:metadata, :property_name) }&.
|
|
79
|
+
dig(:metadata, :property_name)
|
|
80
|
+
|
|
81
|
+
raise "No property name!" unless @property_name
|
|
82
|
+
|
|
83
|
+
@property_name
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def dependency_set
|
|
87
|
+
@dependency_set ||= dependencies.first.requirements.
|
|
88
|
+
find { |r| r.dig(:metadata, :dependency_set) }&.
|
|
89
|
+
dig(:metadata, :dependency_set)
|
|
90
|
+
|
|
91
|
+
raise "No dependency set!" unless @dependency_set
|
|
92
|
+
|
|
93
|
+
@dependency_set
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def sanitized_requirement(dependency)
|
|
97
|
+
new_library_requirement(dependency).
|
|
98
|
+
delete(" ").
|
|
99
|
+
gsub("!=", "neq-").
|
|
100
|
+
gsub(">=", "gte-").
|
|
101
|
+
gsub("<=", "lte-").
|
|
102
|
+
gsub("~>", "tw-").
|
|
103
|
+
gsub("^", "tw-").
|
|
104
|
+
gsub("||", "or-").
|
|
105
|
+
gsub("~", "approx-").
|
|
106
|
+
gsub("~=", "tw-").
|
|
107
|
+
gsub(/==*/, "eq-").
|
|
108
|
+
gsub(">", "gt-").
|
|
109
|
+
gsub("<", "lt-").
|
|
110
|
+
gsub("*", "star").
|
|
111
|
+
gsub(",", "-and-")
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def new_version(dependency)
|
|
115
|
+
if dependency.version.match?(/^[0-9a-f]{40}$/)
|
|
116
|
+
return new_ref(dependency) if ref_changed?(dependency)
|
|
117
|
+
|
|
118
|
+
dependency.version[0..6]
|
|
119
|
+
elsif dependency.version == dependency.previous_version &&
|
|
120
|
+
package_manager == "docker"
|
|
121
|
+
dependency.requirements.
|
|
122
|
+
map { |r| r.dig(:source, "digest") || r.dig(:source, :digest) }.
|
|
123
|
+
compact.first.split(":").last[0..6]
|
|
124
|
+
else
|
|
125
|
+
dependency.version
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def previous_ref(dependency)
|
|
130
|
+
dependency.previous_requirements.map do |r|
|
|
131
|
+
r.dig(:source, "ref") || r.dig(:source, :ref)
|
|
132
|
+
end.compact.first
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def new_ref(dependency)
|
|
136
|
+
dependency.requirements.map do |r|
|
|
137
|
+
r.dig(:source, "ref") || r.dig(:source, :ref)
|
|
138
|
+
end.compact.first
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def ref_changed?(dependency)
|
|
142
|
+
previous_ref(dependency) && new_ref(dependency) &&
|
|
143
|
+
previous_ref(dependency) != new_ref(dependency)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def new_library_requirement(dependency)
|
|
147
|
+
updated_reqs =
|
|
148
|
+
dependency.requirements - dependency.previous_requirements
|
|
149
|
+
|
|
150
|
+
gemspec =
|
|
151
|
+
updated_reqs.find { |r| r[:file].match?(%r{^[^/]*\.gemspec$}) }
|
|
152
|
+
return gemspec[:requirement] if gemspec
|
|
153
|
+
|
|
154
|
+
updated_reqs.first[:requirement]
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def library?
|
|
158
|
+
if files.map(&:name).any? { |name| name.end_with?(".gemspec") }
|
|
159
|
+
return true
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
dependencies.none?(&:appears_in_lockfile?)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def requirements_changed?(dependency)
|
|
166
|
+
(dependency.requirements - dependency.previous_requirements).any?
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "time"
|
|
4
|
+
require "gpgme"
|
|
5
|
+
require "tmpdir"
|
|
6
|
+
require "dependabot/pull_request_creator"
|
|
7
|
+
|
|
8
|
+
module Dependabot
|
|
9
|
+
class PullRequestCreator
|
|
10
|
+
class CommitSigner
|
|
11
|
+
attr_reader :author_details, :commit_message, :tree_sha, :parent_sha,
|
|
12
|
+
:signature_key
|
|
13
|
+
|
|
14
|
+
def initialize(author_details:, commit_message:, tree_sha:, parent_sha:,
|
|
15
|
+
signature_key:)
|
|
16
|
+
@author_details = author_details
|
|
17
|
+
@commit_message = commit_message
|
|
18
|
+
@tree_sha = tree_sha
|
|
19
|
+
@parent_sha = parent_sha
|
|
20
|
+
@signature_key = signature_key
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def signature
|
|
24
|
+
email = author_details[:email]
|
|
25
|
+
|
|
26
|
+
dir = Dir.mktmpdir
|
|
27
|
+
|
|
28
|
+
GPGME::Engine.home_dir = dir
|
|
29
|
+
GPGME::Key.import(signature_key)
|
|
30
|
+
|
|
31
|
+
crypto = GPGME::Crypto.new(armor: true)
|
|
32
|
+
opts = { mode: GPGME::SIG_MODE_DETACH, signer: email }
|
|
33
|
+
crypto.sign(commit_object, opts).to_s
|
|
34
|
+
rescue Errno::ENOTEMPTY
|
|
35
|
+
FileUtils.remove_entry(dir, true)
|
|
36
|
+
# This appears to be a Ruby bug which occurs very rarely
|
|
37
|
+
raise if @retrying
|
|
38
|
+
|
|
39
|
+
@retrying = true
|
|
40
|
+
retry
|
|
41
|
+
ensure
|
|
42
|
+
FileUtils.remove_entry(dir, true)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def commit_object
|
|
48
|
+
time_str = Time.parse(author_details[:date]).strftime("%s %z")
|
|
49
|
+
name = author_details[:name]
|
|
50
|
+
email = author_details[:email]
|
|
51
|
+
|
|
52
|
+
[
|
|
53
|
+
"tree #{tree_sha}",
|
|
54
|
+
"parent #{parent_sha}",
|
|
55
|
+
"author #{name} <#{email}> #{time_str}",
|
|
56
|
+
"committer #{name} <#{email}> #{time_str}",
|
|
57
|
+
"",
|
|
58
|
+
commit_message
|
|
59
|
+
].join("\n")
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "octokit"
|
|
4
|
+
require "securerandom"
|
|
5
|
+
require "dependabot/clients/github_with_retries"
|
|
6
|
+
require "dependabot/pull_request_creator"
|
|
7
|
+
require "dependabot/pull_request_creator/commit_signer"
|
|
8
|
+
|
|
9
|
+
module Dependabot
|
|
10
|
+
class PullRequestCreator
|
|
11
|
+
class Github
|
|
12
|
+
attr_reader :source, :branch_name, :base_commit, :credentials,
|
|
13
|
+
:files, :pr_description, :pr_name, :commit_message,
|
|
14
|
+
:author_details, :signature_key,
|
|
15
|
+
:labeler, :reviewers, :assignees, :milestone
|
|
16
|
+
|
|
17
|
+
def initialize(source:, branch_name:, base_commit:, credentials:,
|
|
18
|
+
files:, commit_message:, pr_description:, pr_name:,
|
|
19
|
+
author_details:, signature_key:,
|
|
20
|
+
labeler:, reviewers:, assignees:, milestone:)
|
|
21
|
+
@source = source
|
|
22
|
+
@branch_name = branch_name
|
|
23
|
+
@base_commit = base_commit
|
|
24
|
+
@credentials = credentials
|
|
25
|
+
@files = files
|
|
26
|
+
@commit_message = commit_message
|
|
27
|
+
@pr_description = pr_description
|
|
28
|
+
@pr_name = pr_name
|
|
29
|
+
@author_details = author_details
|
|
30
|
+
@signature_key = signature_key
|
|
31
|
+
@labeler = labeler
|
|
32
|
+
@reviewers = reviewers
|
|
33
|
+
@assignees = assignees
|
|
34
|
+
@milestone = milestone
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def create
|
|
38
|
+
return if branch_exists? && pull_request_exists?
|
|
39
|
+
|
|
40
|
+
commit = create_commit
|
|
41
|
+
branch = create_or_update_branch(commit)
|
|
42
|
+
return unless branch
|
|
43
|
+
|
|
44
|
+
pull_request = create_pull_request
|
|
45
|
+
return unless pull_request
|
|
46
|
+
|
|
47
|
+
annotate_pull_request(pull_request)
|
|
48
|
+
|
|
49
|
+
pull_request
|
|
50
|
+
rescue Octokit::Error => error
|
|
51
|
+
handle_error(error)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def github_client_for_source
|
|
57
|
+
@github_client_for_source ||=
|
|
58
|
+
Dependabot::Clients::GithubWithRetries.for_source(
|
|
59
|
+
source: source,
|
|
60
|
+
credentials: credentials
|
|
61
|
+
)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def branch_exists?
|
|
65
|
+
@branch_ref ||=
|
|
66
|
+
github_client_for_source.ref(source.repo, "heads/#{branch_name}")
|
|
67
|
+
if @branch_ref.is_a?(Array)
|
|
68
|
+
@branch_ref.any? { |r| r.ref == "refs/heads/#{branch_name}" }
|
|
69
|
+
else
|
|
70
|
+
@branch_ref.ref == "refs/heads/#{branch_name}"
|
|
71
|
+
end
|
|
72
|
+
rescue Octokit::NotFound
|
|
73
|
+
false
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def pull_request_exists?
|
|
77
|
+
github_client_for_source.pull_requests(
|
|
78
|
+
source.repo,
|
|
79
|
+
head: "#{source.repo.split('/').first}:#{branch_name}",
|
|
80
|
+
state: "all"
|
|
81
|
+
).any?
|
|
82
|
+
rescue Octokit::InternalServerError
|
|
83
|
+
# A GitHub bug sometimes means adding `state: all` causes problems.
|
|
84
|
+
# In that case, fall back to making two separate requests.
|
|
85
|
+
open_prs = github_client_for_source.pull_requests(
|
|
86
|
+
source.repo,
|
|
87
|
+
head: "#{source.repo.split('/').first}:#{branch_name}",
|
|
88
|
+
state: "open"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
closed_prs = github_client_for_source.pull_requests(
|
|
92
|
+
source.repo,
|
|
93
|
+
head: "#{source.repo.split('/').first}:#{branch_name}",
|
|
94
|
+
state: "closed"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
[*open_prs, *closed_prs].any?
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def repo_exists?
|
|
101
|
+
github_client_for_source.repo(source.repo)
|
|
102
|
+
true
|
|
103
|
+
rescue Octokit::NotFound
|
|
104
|
+
false
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def create_commit
|
|
108
|
+
tree = create_tree
|
|
109
|
+
|
|
110
|
+
options = author_details&.any? ? { author: author_details } : {}
|
|
111
|
+
|
|
112
|
+
if options[:author]&.any? && signature_key
|
|
113
|
+
options[:author][:date] = Time.now.utc.iso8601
|
|
114
|
+
options[:signature] = commit_signature(tree, options[:author])
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
github_client_for_source.create_commit(
|
|
118
|
+
source.repo,
|
|
119
|
+
commit_message,
|
|
120
|
+
tree.sha,
|
|
121
|
+
base_commit,
|
|
122
|
+
options
|
|
123
|
+
)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def create_tree
|
|
127
|
+
file_trees = files.map do |file|
|
|
128
|
+
if file.type == "submodule"
|
|
129
|
+
{
|
|
130
|
+
path: file.path.sub(%r{^/}, ""),
|
|
131
|
+
mode: "160000",
|
|
132
|
+
type: "commit",
|
|
133
|
+
sha: file.content
|
|
134
|
+
}
|
|
135
|
+
else
|
|
136
|
+
{
|
|
137
|
+
path: file.path.sub(%r{^/}, ""),
|
|
138
|
+
mode: "100644",
|
|
139
|
+
type: "blob",
|
|
140
|
+
content: file.content
|
|
141
|
+
}
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
github_client_for_source.create_tree(
|
|
146
|
+
source.repo,
|
|
147
|
+
file_trees,
|
|
148
|
+
base_tree: base_commit
|
|
149
|
+
)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def create_or_update_branch(commit)
|
|
153
|
+
branch_exists? ? update_branch(commit) : create_branch(commit)
|
|
154
|
+
rescue Octokit::UnprocessableEntity
|
|
155
|
+
# A race condition may cause GitHub to fail here, in which case we retry
|
|
156
|
+
retry_count ||= 0
|
|
157
|
+
retry_count += 1
|
|
158
|
+
retry unless retry_count >= 2
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def create_branch(commit)
|
|
162
|
+
github_client_for_source.create_ref(
|
|
163
|
+
source.repo,
|
|
164
|
+
"heads/#{branch_name}",
|
|
165
|
+
commit.sha
|
|
166
|
+
)
|
|
167
|
+
rescue Octokit::UnprocessableEntity => error
|
|
168
|
+
# Return quietly in the case of a race
|
|
169
|
+
return nil if error.message.match?(/Reference already exists/i)
|
|
170
|
+
raise if @retrying_branch_creation
|
|
171
|
+
|
|
172
|
+
@retrying_branch_creation = true
|
|
173
|
+
|
|
174
|
+
# Branch creation will fail if a branch called `dependabot` already
|
|
175
|
+
# exists, since git won't be able to create a folder with the same name
|
|
176
|
+
@branch_name = SecureRandom.hex[0..3] + @branch_name
|
|
177
|
+
retry
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def update_branch(commit)
|
|
181
|
+
github_client_for_source.update_ref(
|
|
182
|
+
source.repo,
|
|
183
|
+
"heads/#{branch_name}",
|
|
184
|
+
commit.sha,
|
|
185
|
+
true
|
|
186
|
+
)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def annotate_pull_request(pull_request)
|
|
190
|
+
labeler.label_pull_request(pull_request.number)
|
|
191
|
+
add_reviewers_to_pull_request(pull_request) if reviewers&.any?
|
|
192
|
+
add_assignees_to_pull_request(pull_request) if assignees&.any?
|
|
193
|
+
add_milestone_to_pull_request(pull_request) if milestone
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def add_reviewers_to_pull_request(pull_request)
|
|
197
|
+
reviewers_hash =
|
|
198
|
+
Hash[reviewers.keys.map { |k| [k.to_sym, reviewers[k]] }]
|
|
199
|
+
|
|
200
|
+
github_client_for_source.request_pull_request_review(
|
|
201
|
+
source.repo,
|
|
202
|
+
pull_request.number,
|
|
203
|
+
reviewers: reviewers_hash[:reviewers] || [],
|
|
204
|
+
team_reviewers: reviewers_hash[:team_reviewers] || []
|
|
205
|
+
)
|
|
206
|
+
rescue Octokit::UnprocessableEntity => error
|
|
207
|
+
return if error.message.include?("not a collaborator")
|
|
208
|
+
return if error.message.include?("Could not resolve to a node")
|
|
209
|
+
|
|
210
|
+
raise
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def add_assignees_to_pull_request(pull_request)
|
|
214
|
+
github_client_for_source.add_assignees(
|
|
215
|
+
source.repo,
|
|
216
|
+
pull_request.number,
|
|
217
|
+
assignees
|
|
218
|
+
)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def add_milestone_to_pull_request(pull_request)
|
|
222
|
+
github_client_for_source.update_issue(
|
|
223
|
+
source.repo,
|
|
224
|
+
pull_request.number,
|
|
225
|
+
milestone: milestone
|
|
226
|
+
)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def create_pull_request
|
|
230
|
+
github_client_for_source.create_pull_request(
|
|
231
|
+
source.repo,
|
|
232
|
+
source.branch || default_branch,
|
|
233
|
+
branch_name,
|
|
234
|
+
pr_name,
|
|
235
|
+
pr_description
|
|
236
|
+
)
|
|
237
|
+
rescue Octokit::UnprocessableEntity => error
|
|
238
|
+
# Ignore races that we lose
|
|
239
|
+
raise unless error.message.include?("pull request already exists")
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def default_branch
|
|
243
|
+
@default_branch ||=
|
|
244
|
+
github_client_for_source.repository(source.repo).default_branch
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def commit_signature(tree, author_details_with_date)
|
|
248
|
+
CommitSigner.new(
|
|
249
|
+
author_details: author_details_with_date,
|
|
250
|
+
commit_message: commit_message,
|
|
251
|
+
tree_sha: tree.sha,
|
|
252
|
+
parent_sha: base_commit,
|
|
253
|
+
signature_key: signature_key
|
|
254
|
+
).signature
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def handle_error(error)
|
|
258
|
+
case error
|
|
259
|
+
when Octokit::Forbidden
|
|
260
|
+
raise error unless error.message.include?("Repository was archived")
|
|
261
|
+
|
|
262
|
+
raise RepoArchived, error.message
|
|
263
|
+
when Octokit::NotFound
|
|
264
|
+
raise error if repo_exists?
|
|
265
|
+
|
|
266
|
+
raise RepoNotFound, error.message
|
|
267
|
+
when Octokit::UnprocessableEntity
|
|
268
|
+
raise error unless error.message.include?("no history in common")
|
|
269
|
+
|
|
270
|
+
raise NoHistoryInCommon, error.message
|
|
271
|
+
else
|
|
272
|
+
raise error
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
end
|