dependabot-common 0.95.1 → 0.95.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/lib/dependabot.rb +4 -0
  3. data/lib/dependabot/clients/bitbucket.rb +105 -0
  4. data/lib/dependabot/clients/github_with_retries.rb +121 -0
  5. data/lib/dependabot/clients/gitlab.rb +72 -0
  6. data/lib/dependabot/dependency.rb +115 -0
  7. data/lib/dependabot/dependency_file.rb +60 -0
  8. data/lib/dependabot/errors.rb +179 -0
  9. data/lib/dependabot/file_fetchers.rb +18 -0
  10. data/lib/dependabot/file_fetchers/README.md +65 -0
  11. data/lib/dependabot/file_fetchers/base.rb +368 -0
  12. data/lib/dependabot/file_parsers.rb +18 -0
  13. data/lib/dependabot/file_parsers/README.md +45 -0
  14. data/lib/dependabot/file_parsers/base.rb +31 -0
  15. data/lib/dependabot/file_parsers/base/dependency_set.rb +77 -0
  16. data/lib/dependabot/file_updaters.rb +18 -0
  17. data/lib/dependabot/file_updaters/README.md +58 -0
  18. data/lib/dependabot/file_updaters/base.rb +52 -0
  19. data/lib/dependabot/git_commit_checker.rb +412 -0
  20. data/lib/dependabot/metadata_finders.rb +18 -0
  21. data/lib/dependabot/metadata_finders/README.md +53 -0
  22. data/lib/dependabot/metadata_finders/base.rb +117 -0
  23. data/lib/dependabot/metadata_finders/base/changelog_finder.rb +321 -0
  24. data/lib/dependabot/metadata_finders/base/changelog_pruner.rb +177 -0
  25. data/lib/dependabot/metadata_finders/base/commits_finder.rb +221 -0
  26. data/lib/dependabot/metadata_finders/base/release_finder.rb +255 -0
  27. data/lib/dependabot/pull_request_creator.rb +155 -0
  28. data/lib/dependabot/pull_request_creator/branch_namer.rb +170 -0
  29. data/lib/dependabot/pull_request_creator/commit_signer.rb +63 -0
  30. data/lib/dependabot/pull_request_creator/github.rb +277 -0
  31. data/lib/dependabot/pull_request_creator/gitlab.rb +162 -0
  32. data/lib/dependabot/pull_request_creator/labeler.rb +373 -0
  33. data/lib/dependabot/pull_request_creator/message_builder.rb +906 -0
  34. data/lib/dependabot/pull_request_updater.rb +43 -0
  35. data/lib/dependabot/pull_request_updater/github.rb +165 -0
  36. data/lib/dependabot/shared_helpers.rb +224 -0
  37. data/lib/dependabot/source.rb +120 -0
  38. data/lib/dependabot/update_checkers.rb +18 -0
  39. data/lib/dependabot/update_checkers/README.md +67 -0
  40. data/lib/dependabot/update_checkers/base.rb +220 -0
  41. data/lib/dependabot/utils.rb +33 -0
  42. data/lib/dependabot/version.rb +5 -0
  43. data/lib/rubygems_version_patch.rb +14 -0
  44. metadata +44 -2
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dependabot/clients/gitlab"
4
+ require "dependabot/pull_request_creator"
5
+ require "gitlab"
6
+
7
+ module Dependabot
8
+ class PullRequestCreator
9
+ class Gitlab
10
+ attr_reader :source, :branch_name, :base_commit, :credentials,
11
+ :files, :pr_description, :pr_name, :commit_message,
12
+ :author_details, :labeler, :approvers, :assignee,
13
+ :milestone
14
+
15
+ def initialize(source:, branch_name:, base_commit:, credentials:,
16
+ files:, commit_message:, pr_description:, pr_name:,
17
+ author_details:, labeler:, approvers:, assignee:,
18
+ milestone:)
19
+ @source = source
20
+ @branch_name = branch_name
21
+ @base_commit = base_commit
22
+ @credentials = credentials
23
+ @files = files
24
+ @commit_message = commit_message
25
+ @pr_description = pr_description
26
+ @pr_name = pr_name
27
+ @author_details = author_details
28
+ @labeler = labeler
29
+ @approvers = approvers
30
+ @assignee = assignee
31
+ @milestone = milestone
32
+ end
33
+
34
+ def create
35
+ return if branch_exists? && merge_request_exists?
36
+
37
+ if branch_exists?
38
+ create_commit unless commit_exists?
39
+ else
40
+ create_branch
41
+ create_commit
42
+ end
43
+
44
+ labeler.create_default_labels_if_required
45
+ merge_request = create_merge_request
46
+ return unless merge_request
47
+
48
+ annotate_merge_request(merge_request)
49
+
50
+ merge_request
51
+ end
52
+
53
+ private
54
+
55
+ def gitlab_client_for_source
56
+ @gitlab_client_for_source ||= Dependabot::Clients::Gitlab.for_source(
57
+ source: source,
58
+ credentials: credentials
59
+ )
60
+ end
61
+
62
+ def branch_exists?
63
+ @branch_ref ||=
64
+ gitlab_client_for_source.branch(source.repo, branch_name)
65
+ true
66
+ rescue ::Gitlab::Error::NotFound
67
+ false
68
+ end
69
+
70
+ def commit_exists?
71
+ @commits ||=
72
+ gitlab_client_for_source.commits(source.repo, ref_name: branch_name)
73
+ @commits.first.message == commit_message
74
+ end
75
+
76
+ def merge_request_exists?
77
+ gitlab_client_for_source.merge_requests(
78
+ source.repo,
79
+ source_branch: branch_name,
80
+ target_branch: source.branch || default_branch,
81
+ state: "all"
82
+ ).any?
83
+ end
84
+
85
+ def create_branch
86
+ gitlab_client_for_source.create_branch(
87
+ source.repo,
88
+ branch_name,
89
+ base_commit
90
+ )
91
+ end
92
+
93
+ def create_commit
94
+ if files.count == 1 && files.first.type == "submodule"
95
+ return create_submodule_update_commit
96
+ end
97
+
98
+ actions = files.map do |file|
99
+ {
100
+ action: "update",
101
+ file_path: file.path,
102
+ content: file.content
103
+ }
104
+ end
105
+
106
+ gitlab_client_for_source.create_commit(
107
+ source.repo,
108
+ branch_name,
109
+ commit_message,
110
+ actions
111
+ )
112
+ end
113
+
114
+ def create_submodule_update_commit
115
+ file = files.first
116
+
117
+ gitlab_client_for_source.edit_submodule(
118
+ source.repo,
119
+ file.path.gsub(%r{^/}, ""),
120
+ branch: branch_name,
121
+ commit_sha: file.content,
122
+ commit_message: commit_message
123
+ )
124
+ end
125
+
126
+ def create_merge_request
127
+ gitlab_client_for_source.create_merge_request(
128
+ source.repo,
129
+ pr_name,
130
+ source_branch: branch_name,
131
+ target_branch: source.branch || default_branch,
132
+ description: pr_description,
133
+ remove_source_branch: true,
134
+ assignee_id: assignee,
135
+ labels: labeler.labels_for_pr.join(","),
136
+ milestone_id: milestone
137
+ )
138
+ end
139
+
140
+ def annotate_merge_request(merge_request)
141
+ add_approvers_to_merge_request(merge_request) if approvers&.any?
142
+ end
143
+
144
+ def add_approvers_to_merge_request(merge_request)
145
+ approvers_hash =
146
+ Hash[approvers.keys.map { |k| [k.to_sym, approvers[k]] }]
147
+
148
+ gitlab_client_for_source.edit_merge_request_approvers(
149
+ source.repo,
150
+ merge_request.iid,
151
+ approver_ids: approvers_hash[:approvers] || [],
152
+ approver_group_ids: approvers_hash[:group_approvers] || []
153
+ )
154
+ end
155
+
156
+ def default_branch
157
+ @default_branch ||=
158
+ gitlab_client_for_source.project(source.repo).default_branch
159
+ end
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,373 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "gitlab"
4
+ require "octokit"
5
+ require "dependabot/pull_request_creator"
6
+
7
+ # rubocop:disable Metrics/ClassLength
8
+ module Dependabot
9
+ class PullRequestCreator
10
+ class Labeler
11
+ DEPENDENCIES_LABEL_REGEX = %r{^[^/]*dependenc[^/]+$}i.freeze
12
+
13
+ @package_manager_labels = {}
14
+
15
+ class << self
16
+ attr_reader :package_manager_labels
17
+
18
+ def label_details_for_package_manager(package_manager)
19
+ label_details = @package_manager_labels[package_manager]
20
+ return label_details if label_details
21
+
22
+ raise "Unsupported package_manager #{package_manager}"
23
+ end
24
+
25
+ def register_label_details(package_manager, label_details)
26
+ @package_manager_labels[package_manager] = label_details
27
+ end
28
+ end
29
+
30
+ def initialize(source:, custom_labels:, credentials:, dependencies:,
31
+ includes_security_fixes:, label_language:)
32
+ @source = source
33
+ @custom_labels = custom_labels
34
+ @credentials = credentials
35
+ @dependencies = dependencies
36
+ @includes_security_fixes = includes_security_fixes
37
+ @label_language = label_language
38
+ end
39
+
40
+ def create_default_labels_if_required
41
+ create_default_dependencies_label_if_required
42
+ create_default_security_label_if_required
43
+ create_default_language_label_if_required
44
+ end
45
+
46
+ def labels_for_pr
47
+ [
48
+ *default_labels_for_pr,
49
+ includes_security_fixes? ? security_label : nil,
50
+ semver_labels_exist? ? semver_label : nil
51
+ ].compact.uniq
52
+ end
53
+
54
+ def label_pull_request(pull_request_number)
55
+ create_default_labels_if_required
56
+
57
+ return if labels_for_pr.none?
58
+ raise "Only GitHub!" unless source.provider == "github"
59
+
60
+ github_client_for_source.add_labels_to_an_issue(
61
+ source.repo,
62
+ pull_request_number,
63
+ labels_for_pr
64
+ )
65
+ rescue Octokit::UnprocessableEntity
66
+ retrying ||= false
67
+ raise if retrying
68
+
69
+ retrying = true
70
+ retry
71
+ end
72
+
73
+ private
74
+
75
+ attr_reader :source, :custom_labels, :credentials, :dependencies
76
+
77
+ def label_language?
78
+ @label_language
79
+ end
80
+
81
+ def includes_security_fixes?
82
+ @includes_security_fixes
83
+ end
84
+
85
+ # rubocop:disable Metrics/CyclomaticComplexity
86
+ # rubocop:disable Metrics/PerceivedComplexity
87
+ def update_type
88
+ return unless dependencies.any?(&:previous_version)
89
+
90
+ precison = dependencies.map do |dep|
91
+ new_version_parts = version(dep).split(".")
92
+ old_version_parts = previous_version(dep)&.split(".") || []
93
+ all_parts = new_version_parts.first(3) + old_version_parts.first(3)
94
+ next 0 unless all_parts.all? { |part| part.to_i.to_s == part }
95
+ next 1 if new_version_parts[0] != old_version_parts[0]
96
+ next 2 if new_version_parts[1] != old_version_parts[1]
97
+
98
+ 3
99
+ end.min
100
+
101
+ case precison
102
+ when 0 then "non-semver"
103
+ when 1 then "major"
104
+ when 2 then "minor"
105
+ when 3 then "patch"
106
+ end
107
+ end
108
+ # rubocop:enable Metrics/CyclomaticComplexity
109
+ # rubocop:enable Metrics/PerceivedComplexity
110
+
111
+ def version(dep)
112
+ return dep.version if version_class.correct?(dep.version)
113
+
114
+ source = dep.requirements.find { |r| r.fetch(:source) }&.fetch(:source)
115
+ type = source&.fetch("type", nil) || source&.fetch(:type)
116
+ return dep.version unless type == "git"
117
+
118
+ ref = source.fetch("ref", nil) || source.fetch(:ref)
119
+ version_from_ref = ref&.gsub(/^v/, "")
120
+ return dep.version unless version_from_ref
121
+ return dep.version unless version_class.correct?(version_from_ref)
122
+
123
+ version_from_ref
124
+ end
125
+
126
+ def previous_version(dep)
127
+ version_str = dep.previous_version
128
+ return version_str if version_class.correct?(version_str)
129
+
130
+ source = dep.previous_requirements.
131
+ find { |r| r.fetch(:source) }&.fetch(:source)
132
+ type = source&.fetch("type", nil) || source&.fetch(:type)
133
+ return version_str unless type == "git"
134
+
135
+ ref = source.fetch("ref", nil) || source.fetch(:ref)
136
+ version_from_ref = ref&.gsub(/^v/, "")
137
+ return version_str unless version_from_ref
138
+ return version_str unless version_class.correct?(version_from_ref)
139
+
140
+ version_from_ref
141
+ end
142
+
143
+ def create_default_dependencies_label_if_required
144
+ return if custom_labels
145
+ return if dependencies_label_exists?
146
+
147
+ create_dependencies_label
148
+ end
149
+
150
+ def create_default_security_label_if_required
151
+ return unless includes_security_fixes?
152
+ return if security_label_exists?
153
+
154
+ create_security_label
155
+ end
156
+
157
+ def create_default_language_label_if_required
158
+ return unless label_language?
159
+ return if custom_labels
160
+ return if language_label_exists?
161
+
162
+ create_language_label
163
+ end
164
+
165
+ def default_labels_for_pr
166
+ if custom_labels then custom_labels & labels
167
+ else
168
+ [
169
+ labels.find { |l| l.match?(DEPENDENCIES_LABEL_REGEX) },
170
+ label_language? ? language_label : nil
171
+ ].compact
172
+ end
173
+ end
174
+
175
+ def dependencies_label_exists?
176
+ labels.any? { |l| l.match?(DEPENDENCIES_LABEL_REGEX) }
177
+ end
178
+
179
+ def security_label_exists?
180
+ !security_label.nil?
181
+ end
182
+
183
+ def security_label
184
+ labels.find { |l| l.match?(/security/i) }
185
+ end
186
+
187
+ def semver_labels_exist?
188
+ (%w(major minor patch) - labels.map(&:downcase)).empty?
189
+ end
190
+
191
+ def semver_label
192
+ return unless update_type
193
+
194
+ labels.find { |l| l.downcase == update_type.to_s }
195
+ end
196
+
197
+ def language_label_exists?
198
+ !language_label.nil?
199
+ end
200
+
201
+ def language_label
202
+ label_name =
203
+ self.class.label_details_for_package_manager(package_manager).
204
+ fetch(:name)
205
+ labels.find { |l| l.casecmp(label_name).zero? }
206
+ end
207
+
208
+ def labels
209
+ @labels ||=
210
+ case source.provider
211
+ when "github" then fetch_github_labels
212
+ when "gitlab" then fetch_gitlab_labels
213
+ else raise "Unsupported provider #{source.provider}"
214
+ end
215
+ end
216
+
217
+ def fetch_github_labels
218
+ client = github_client_for_source
219
+
220
+ labels =
221
+ client.
222
+ labels(source.repo, per_page: 100).
223
+ map(&:name)
224
+
225
+ next_link = client.last_response.rels[:next]
226
+
227
+ while next_link
228
+ next_page = next_link.get
229
+ labels += next_page.data.map(&:name)
230
+ next_link = next_page.rels[:next]
231
+ end
232
+
233
+ labels
234
+ end
235
+
236
+ def fetch_gitlab_labels
237
+ gitlab_client_for_source.
238
+ labels(source.repo).
239
+ map(&:name)
240
+ end
241
+
242
+ def create_dependencies_label
243
+ case source.provider
244
+ when "github" then create_github_dependencies_label
245
+ when "gitlab" then create_gitlab_dependencies_label
246
+ else raise "Unsupported provider #{source.provider}"
247
+ end
248
+ end
249
+
250
+ def create_security_label
251
+ case source.provider
252
+ when "github" then create_github_security_label
253
+ when "gitlab" then create_gitlab_security_label
254
+ else raise "Unsupported provider #{source.provider}"
255
+ end
256
+ end
257
+
258
+ def create_language_label
259
+ case source.provider
260
+ when "github" then create_github_language_label
261
+ when "gitlab" then create_gitlab_language_label
262
+ else raise "Unsupported provider #{source.provider}"
263
+ end
264
+ end
265
+
266
+ def create_github_dependencies_label
267
+ github_client_for_source.add_label(
268
+ source.repo, "dependencies", "0025ff",
269
+ description: "Pull requests that update a dependency file",
270
+ accept: "application/vnd.github.symmetra-preview+json"
271
+ )
272
+ @labels = [*@labels, "dependencies"].uniq
273
+ rescue Octokit::UnprocessableEntity => error
274
+ raise unless error.errors.first.fetch(:code) == "already_exists"
275
+
276
+ @labels = [*@labels, "dependencies"].uniq
277
+ end
278
+
279
+ def create_gitlab_dependencies_label
280
+ gitlab_client_for_source.create_label(
281
+ source.repo, "dependencies", "#0025ff",
282
+ description: "Pull requests that update a dependency file"
283
+ )
284
+ @labels = [*@labels, "dependencies"].uniq
285
+ end
286
+
287
+ def create_github_security_label
288
+ github_client_for_source.add_label(
289
+ source.repo, "security", "ee0701",
290
+ description: "Pull requests that address a security vulnerability",
291
+ accept: "application/vnd.github.symmetra-preview+json"
292
+ )
293
+ @labels = [*@labels, "security"].uniq
294
+ rescue Octokit::UnprocessableEntity => error
295
+ raise unless error.errors.first.fetch(:code) == "already_exists"
296
+
297
+ @labels = [*@labels, "security"].uniq
298
+ end
299
+
300
+ def create_gitlab_security_label
301
+ gitlab_client_for_source.create_label(
302
+ source.repo, "security", "#ee0701",
303
+ description: "Pull requests that address a security vulnerability"
304
+ )
305
+ @labels = [*@labels, "security"].uniq
306
+ end
307
+
308
+ def create_github_language_label
309
+ langauge_name =
310
+ self.class.label_details_for_package_manager(package_manager).
311
+ fetch(:name)
312
+ github_client_for_source.add_label(
313
+ source.repo,
314
+ langauge_name,
315
+ self.class.label_details_for_package_manager(package_manager).
316
+ fetch(:colour),
317
+ description: "Pull requests that update #{langauge_name.capitalize} "\
318
+ "code",
319
+ accept: "application/vnd.github.symmetra-preview+json"
320
+ )
321
+ @labels = [*@labels, langauge_name].uniq
322
+ rescue Octokit::UnprocessableEntity => error
323
+ raise unless error.errors.first.fetch(:code) == "already_exists"
324
+
325
+ @labels = [*@labels, langauge_name].uniq
326
+ end
327
+
328
+ def create_gitlab_language_label
329
+ langauge_name =
330
+ self.class.label_details_for_package_manager(package_manager).
331
+ fetch(:name)
332
+ gitlab_client_for_source.create_label(
333
+ source.repo,
334
+ langauge_name,
335
+ "#" + self.class.label_details_for_package_manager(package_manager).
336
+ fetch(:colour)
337
+ )
338
+ @labels = [*@labels, langauge_name].uniq
339
+ end
340
+
341
+ def github_client_for_source
342
+ @github_client_for_source ||=
343
+ Dependabot::Clients::GithubWithRetries.for_source(
344
+ source: source,
345
+ credentials: credentials
346
+ )
347
+ end
348
+
349
+ def gitlab_client_for_source
350
+ access_token =
351
+ credentials.
352
+ select { |cred| cred["type"] == "git_source" }.
353
+ find { |cred| cred["host"] == source.hostname }&.
354
+ fetch("password")
355
+
356
+ @gitlab_client_for_source ||=
357
+ ::Gitlab.client(
358
+ endpoint: source.api_endpoint,
359
+ private_token: access_token || ""
360
+ )
361
+ end
362
+
363
+ def package_manager
364
+ @package_manager ||= dependencies.first.package_manager
365
+ end
366
+
367
+ def version_class
368
+ Utils.version_class_for_package_manager(package_manager)
369
+ end
370
+ end
371
+ end
372
+ end
373
+ # rubocop:enable Metrics/ClassLength