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.
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