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,177 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dependabot/metadata_finders/base"
4
+
5
+ module Dependabot
6
+ module MetadataFinders
7
+ class Base
8
+ class ChangelogPruner
9
+ attr_reader :dependency, :changelog_text
10
+
11
+ def initialize(dependency:, changelog_text:)
12
+ @dependency = dependency
13
+ @changelog_text = changelog_text
14
+ end
15
+
16
+ def includes_new_version?
17
+ !new_version_changelog_line.nil?
18
+ end
19
+
20
+ # rubocop:disable Metrics/PerceivedComplexity
21
+ # rubocop:disable Metrics/CyclomaticComplexity
22
+ def pruned_text
23
+ changelog_lines = changelog_text.split("\n")
24
+
25
+ slice_range =
26
+ if old_version_changelog_line && new_version_changelog_line
27
+ if old_version_changelog_line < new_version_changelog_line
28
+ Range.new(old_version_changelog_line, -1)
29
+ else
30
+ Range.new(new_version_changelog_line,
31
+ old_version_changelog_line - 1)
32
+ end
33
+ elsif old_version_changelog_line
34
+ return if old_version_changelog_line.zero?
35
+
36
+ # Assumes changelog is in descending order
37
+ Range.new(0, old_version_changelog_line - 1)
38
+ elsif new_version_changelog_line
39
+ # Assumes changelog is in descending order
40
+ Range.new(new_version_changelog_line, -1)
41
+ else
42
+ return unless changelog_contains_relevant_versions?
43
+
44
+ # If the changelog contains any relevant versions, return it in
45
+ # full. We could do better here by fully parsing the changelog
46
+ Range.new(0, -1)
47
+ end
48
+
49
+ changelog_lines.slice(slice_range).join("\n").sub(/\n*\z/, "")
50
+ end
51
+ # rubocop:enable Metrics/PerceivedComplexity
52
+ # rubocop:enable Metrics/CyclomaticComplexity
53
+
54
+ private
55
+
56
+ def old_version_changelog_line
57
+ old_version = git_source? ? previous_ref : dependency.previous_version
58
+ return nil unless old_version
59
+
60
+ changelog_line_for_version(old_version)
61
+ end
62
+
63
+ def new_version_changelog_line
64
+ return nil unless new_version
65
+
66
+ changelog_line_for_version(new_version)
67
+ end
68
+
69
+ # rubocop:disable Metrics/CyclomaticComplexity
70
+ # rubocop:disable Metrics/PerceivedComplexity
71
+ def changelog_line_for_version(version)
72
+ raise "No changelog text" unless changelog_text
73
+ return nil unless version
74
+
75
+ version = version.gsub(/^v/, "")
76
+ escaped_version = Regexp.escape(version)
77
+
78
+ changelog_lines = changelog_text.split("\n")
79
+
80
+ changelog_lines.find_index.with_index do |line, index|
81
+ next false unless line.match?(/(?<!\.)#{escaped_version}(?![.\-])/)
82
+ next false if line.match?(/#{escaped_version}\.\./)
83
+ next true if line.start_with?("#", "!", "==")
84
+ next true if line.match?(/^v?#{escaped_version}:?/)
85
+ next true if line.match?(/^[\+\*\-] (version )?#{escaped_version}/i)
86
+ next true if line.match?(/^\d{4}-\d{2}-\d{2}/)
87
+ next true if changelog_lines[index + 1]&.match?(/^[=\-\+]{3,}\s*$/)
88
+
89
+ false
90
+ end
91
+ end
92
+ # rubocop:enable Metrics/CyclomaticComplexity
93
+ # rubocop:enable Metrics/PerceivedComplexity
94
+
95
+ def changelog_contains_relevant_versions?
96
+ # Assume the changelog is relevant if we can't parse the new version
97
+ return true unless version_class.correct?(dependency.version)
98
+
99
+ # Assume the changelog is relevant if it mentions the new version
100
+ # anywhere
101
+ return true if changelog_text.include?(dependency.version)
102
+
103
+ # Otherwise check if any intermediate versions are included in headers
104
+ versions_in_changelog_headers.any? do |version|
105
+ next false unless version <= version_class.new(dependency.version)
106
+ next true unless dependency.previous_version
107
+ next true unless version_class.correct?(dependency.previous_version)
108
+
109
+ version > version_class.new(dependency.previous_version)
110
+ end
111
+ end
112
+
113
+ def versions_in_changelog_headers
114
+ changelog_lines = changelog_text.split("\n")
115
+ header_lines =
116
+ changelog_lines.select.with_index do |line, index|
117
+ next true if line.start_with?("#", "!")
118
+ next true if line.match?(/^v?\d\.\d/)
119
+ next true if changelog_lines[index + 1]&.match?(/^[=-]+\s*$/)
120
+
121
+ false
122
+ end
123
+
124
+ versions = []
125
+ header_lines.each do |line|
126
+ cleaned_line = line.gsub(/^[^0-9]*/, "").gsub(/[\s,:].*/, "")
127
+ next if cleaned_line.empty? || !version_class.correct?(cleaned_line)
128
+
129
+ versions << version_class.new(cleaned_line)
130
+ end
131
+
132
+ versions
133
+ end
134
+
135
+ def new_version
136
+ @new_version ||= git_source? ? new_ref : dependency.version
137
+ @new_version&.gsub(/^v/, "")
138
+ end
139
+
140
+ def previous_ref
141
+ dependency.previous_requirements.map do |r|
142
+ r.dig(:source, "ref") || r.dig(:source, :ref)
143
+ end.compact.first
144
+ end
145
+
146
+ def new_ref
147
+ dependency.requirements.map do |r|
148
+ r.dig(:source, "ref") || r.dig(:source, :ref)
149
+ end.compact.first
150
+ end
151
+
152
+ def ref_changed?
153
+ previous_ref && new_ref && previous_ref != new_ref
154
+ end
155
+
156
+ # TODO: Refactor me so that Composer doesn't need to be special cased
157
+ def git_source?
158
+ # Special case Composer, which uses git as a source but handles tags
159
+ # internally
160
+ return false if dependency.package_manager == "composer"
161
+
162
+ requirements = dependency.requirements
163
+ sources = requirements.map { |r| r.fetch(:source) }.uniq.compact
164
+ return false if sources.empty?
165
+ raise "Multiple sources! #{sources.join(', ')}" if sources.count > 1
166
+
167
+ source_type = sources.first[:type] || sources.first.fetch("type")
168
+ source_type == "git"
169
+ end
170
+
171
+ def version_class
172
+ Utils.version_class_for_package_manager(dependency.package_manager)
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,221 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dependabot/clients/github_with_retries"
4
+ require "dependabot/clients/gitlab"
5
+ require "dependabot/clients/bitbucket"
6
+ require "dependabot/shared_helpers"
7
+ require "dependabot/metadata_finders/base"
8
+
9
+ module Dependabot
10
+ module MetadataFinders
11
+ class Base
12
+ class CommitsFinder
13
+ attr_reader :source, :dependency, :credentials
14
+
15
+ def initialize(source:, dependency:, credentials:)
16
+ @source = source
17
+ @dependency = dependency
18
+ @credentials = credentials
19
+ end
20
+
21
+ def commits_url
22
+ return unless source
23
+ return if source.provider == "azure" # TODO: Fetch Azure commits
24
+
25
+ path =
26
+ case source.provider
27
+ when "github" then github_compare_path(new_tag, previous_tag)
28
+ when "bitbucket" then bitbucket_compare_path(new_tag, previous_tag)
29
+ when "gitlab" then gitlab_compare_path(new_tag, previous_tag)
30
+ else raise "Unexpected source provider '#{source.provider}'"
31
+ end
32
+
33
+ "#{source.url}/#{path}"
34
+ end
35
+
36
+ # rubocop:disable Metrics/CyclomaticComplexity
37
+ def commits
38
+ return [] unless source
39
+ return [] unless new_tag && previous_tag
40
+
41
+ case source.provider
42
+ when "github" then fetch_github_commits
43
+ when "bitbucket" then fetch_bitbucket_commits
44
+ when "gitlab" then fetch_gitlab_commits
45
+ when "azure" then [] # TODO: Fetch Azure commits
46
+ else raise "Unexpected source provider '#{source.provider}'"
47
+ end
48
+ end
49
+ # rubocop:enable Metrics/CyclomaticComplexity
50
+
51
+ def new_tag
52
+ new_version = dependency.version
53
+
54
+ if git_source?(dependency.requirements) then new_version
55
+ else
56
+ tags = dependency_tags.
57
+ select { |t| t =~ version_regex(new_version) }
58
+ tags.find { |t| t.include?(dependency.name) } || tags.first
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def previous_tag
65
+ previous_version = dependency.previous_version
66
+
67
+ if git_source?(dependency.previous_requirements)
68
+ previous_version || previous_ref
69
+ else
70
+ tags = dependency_tags.
71
+ select { |t| t =~ version_regex(previous_version) }
72
+ tags.find { |t| t.include?(dependency.name) } || tags.first
73
+ end
74
+ end
75
+
76
+ # TODO: Refactor me so that Composer doesn't need to be special cased
77
+ def git_source?(requirements)
78
+ # Special case Composer, which uses git as a source but handles tags
79
+ # internally
80
+ return false if dependency.package_manager == "composer"
81
+
82
+ sources = requirements.map { |r| r.fetch(:source) }.uniq.compact
83
+ return false if sources.empty?
84
+ raise "Multiple sources! #{sources.join(', ')}" if sources.count > 1
85
+
86
+ source_type = sources.first[:type] || sources.first.fetch("type")
87
+ source_type == "git"
88
+ end
89
+
90
+ def previous_ref
91
+ return unless git_source?(dependency.previous_requirements)
92
+
93
+ dependency.previous_requirements.map do |r|
94
+ r.dig(:source, "ref") || r.dig(:source, :ref)
95
+ end.compact.first
96
+ end
97
+
98
+ def version_regex(version)
99
+ /(?:[^0-9\.]|\A)#{Regexp.escape(version || "unknown")}\z/
100
+ end
101
+
102
+ def dependency_tags
103
+ @dependency_tags ||= fetch_dependency_tags
104
+ end
105
+
106
+ def fetch_dependency_tags
107
+ return [] unless source
108
+
109
+ case source.provider
110
+ when "github"
111
+ github_client.tags(source.repo, per_page: 100).map(&:name)
112
+ when "bitbucket"
113
+ bitbucket_client.tags(source.repo).map { |tag| tag["name"] }
114
+ when "gitlab"
115
+ gitlab_client.tags(source.repo).map(&:name)
116
+ when "azure"
117
+ [] # TODO: Fetch Azure tags
118
+ else raise "Unexpected source provider '#{source.provider}'"
119
+ end
120
+ rescue Octokit::NotFound, Gitlab::Error::NotFound,
121
+ Dependabot::Clients::Bitbucket::NotFound,
122
+ Dependabot::Clients::Bitbucket::Unauthorized,
123
+ Dependabot::Clients::Bitbucket::Forbidden
124
+ []
125
+ end
126
+
127
+ def github_compare_path(new_tag, previous_tag)
128
+ if new_tag && previous_tag
129
+ "compare/#{previous_tag}...#{new_tag}"
130
+ elsif new_tag
131
+ "commits/#{new_tag}"
132
+ else
133
+ "commits"
134
+ end
135
+ end
136
+
137
+ def bitbucket_compare_path(new_tag, previous_tag)
138
+ if new_tag && previous_tag
139
+ "branches/compare/#{new_tag}..#{previous_tag}"
140
+ elsif new_tag
141
+ "commits/tag/#{new_tag}"
142
+ else
143
+ "commits"
144
+ end
145
+ end
146
+
147
+ def gitlab_compare_path(new_tag, previous_tag)
148
+ if new_tag && previous_tag
149
+ "compare/#{previous_tag}...#{new_tag}"
150
+ elsif new_tag
151
+ "commits/#{new_tag}"
152
+ else
153
+ "commits/master"
154
+ end
155
+ end
156
+
157
+ def fetch_github_commits
158
+ commits =
159
+ github_client.compare(source.repo, previous_tag, new_tag).commits
160
+ return [] unless commits
161
+
162
+ commits.map do |commit|
163
+ {
164
+ message: commit.commit.message,
165
+ sha: commit.sha,
166
+ html_url: commit.html_url
167
+ }
168
+ end
169
+ rescue Octokit::NotFound
170
+ []
171
+ end
172
+
173
+ def fetch_bitbucket_commits
174
+ bitbucket_client.
175
+ compare(source.repo, previous_tag, new_tag).
176
+ map do |commit|
177
+ {
178
+ message: commit.dig("summary", "raw"),
179
+ sha: commit["hash"],
180
+ html_url: commit.dig("links", "html", "href")
181
+ }
182
+ end
183
+ rescue Dependabot::Clients::Bitbucket::NotFound,
184
+ Dependabot::Clients::Bitbucket::Unauthorized,
185
+ Dependabot::Clients::Bitbucket::Forbidden
186
+ []
187
+ end
188
+
189
+ def fetch_gitlab_commits
190
+ gitlab_client.
191
+ compare(source.repo, previous_tag, new_tag).
192
+ commits.
193
+ map do |commit|
194
+ {
195
+ message: commit["message"],
196
+ sha: commit["id"],
197
+ html_url: "#{source.url}/commit/#{commit['id']}"
198
+ }
199
+ end
200
+ rescue Gitlab::Error::NotFound
201
+ []
202
+ end
203
+
204
+ def gitlab_client
205
+ @gitlab_client ||= Dependabot::Clients::Gitlab.
206
+ for_gitlab_dot_com(credentials: credentials)
207
+ end
208
+
209
+ def github_client
210
+ @github_client ||= Dependabot::Clients::GithubWithRetries.
211
+ for_github_dot_com(credentials: credentials)
212
+ end
213
+
214
+ def bitbucket_client
215
+ @bitbucket_client ||= Dependabot::Clients::Bitbucket.
216
+ for_bitbucket_dot_org(credentials: credentials)
217
+ end
218
+ end
219
+ end
220
+ end
221
+ end
@@ -0,0 +1,255 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dependabot/clients/github_with_retries"
4
+ require "dependabot/clients/gitlab"
5
+ require "dependabot/metadata_finders/base"
6
+ require "dependabot/utils"
7
+
8
+ module Dependabot
9
+ module MetadataFinders
10
+ class Base
11
+ class ReleaseFinder
12
+ attr_reader :dependency, :credentials, :source
13
+
14
+ def initialize(source:, dependency:, credentials:)
15
+ @source = source
16
+ @dependency = dependency
17
+ @credentials = credentials
18
+ end
19
+
20
+ def releases_url
21
+ return unless source
22
+
23
+ case source.provider
24
+ when "github" then "#{source.url}/releases"
25
+ when "gitlab" then "#{source.url}/tags"
26
+ when "bitbucket" then nil
27
+ when "azure" then "#{source.url}/tags"
28
+ else raise "Unexpected repo provider '#{source.provider}'"
29
+ end
30
+ end
31
+
32
+ def releases_text
33
+ return unless relevant_releases.any?
34
+ return if relevant_releases.all? { |r| r.body.nil? || r.body == "" }
35
+
36
+ relevant_releases.map { |r| serialize_release(r) }.join("\n\n")
37
+ end
38
+
39
+ private
40
+
41
+ def all_dep_releases
42
+ releases = all_releases
43
+ dep_prefix = dependency.name.downcase
44
+
45
+ releases_with_dependency_name =
46
+ releases.
47
+ reject { |r| r.tag_name.nil? }.
48
+ select { |r| r.tag_name.downcase.include?(dep_prefix) }
49
+
50
+ return releases unless releases_with_dependency_name.any?
51
+
52
+ releases_with_dependency_name
53
+ end
54
+
55
+ def all_releases
56
+ @all_releases ||= fetch_dependency_releases
57
+ end
58
+
59
+ def relevant_releases
60
+ releases = releases_since_previous_version
61
+
62
+ # Sometimes we can't filter the releases properly (if they're
63
+ # prefixed by a number that gets confused with the version). In this
64
+ # case, the best we can do is return nil.
65
+ return [] unless releases.any?
66
+
67
+ if updated_release && version_class.correct?(dependency.version)
68
+ releases = filter_releases_using_updated_release(releases)
69
+ filter_releases_using_updated_version(releases, conservative: true)
70
+ elsif updated_release
71
+ filter_releases_using_updated_release(releases)
72
+ elsif version_class.correct?(dependency.version)
73
+ filter_releases_using_updated_version(releases, conservative: false)
74
+ else
75
+ [updated_release].compact
76
+ end
77
+ end
78
+
79
+ def releases_since_previous_version
80
+ previous_version = dependency.previous_version
81
+ return [updated_release].compact unless previous_version
82
+
83
+ if previous_release && version_class.correct?(previous_version)
84
+ releases = filter_releases_using_previous_release(all_dep_releases)
85
+ filter_releases_using_previous_version(releases, conservative: true)
86
+ elsif previous_release
87
+ filter_releases_using_previous_release(all_dep_releases)
88
+ elsif version_class.correct?(previous_version)
89
+ filter_releases_using_previous_version(
90
+ all_dep_releases,
91
+ conservative: false
92
+ )
93
+ else
94
+ [updated_release].compact
95
+ end
96
+ end
97
+
98
+ def filter_releases_using_previous_release(releases)
99
+ return releases if releases.index(previous_release).nil?
100
+
101
+ releases.first(releases.index(previous_release))
102
+ end
103
+
104
+ def filter_releases_using_updated_release(releases)
105
+ return releases if releases.index(updated_release).nil?
106
+
107
+ releases[releases.index(updated_release)..-1]
108
+ end
109
+
110
+ def filter_releases_using_previous_version(releases, conservative:)
111
+ previous_version = version_class.new(dependency.previous_version)
112
+
113
+ releases.reject do |release|
114
+ cleaned_tag = release.tag_name.gsub(/^[^0-9]*/, "")
115
+ cleaned_name = release.name&.gsub(/^[^0-9]*/, "")
116
+
117
+ tag_version = [cleaned_tag, cleaned_name].compact.reject(&:empty?).
118
+ select { |nm| version_class.correct?(nm) }.
119
+ map { |nm| version_class.new(nm) }.max
120
+
121
+ next conservative unless tag_version
122
+
123
+ # Reject any releases that are less than the previous version
124
+ # (e.g., if two major versions are being maintained)
125
+ tag_version <= previous_version
126
+ end
127
+ end
128
+
129
+ def filter_releases_using_updated_version(releases, conservative:)
130
+ updated_version = version_class.new(dependency.version)
131
+
132
+ releases.reject do |release|
133
+ cleaned_tag = release.tag_name.gsub(/^[^0-9]*/, "")
134
+ cleaned_name = release.name&.gsub(/^[^0-9]*/, "")
135
+
136
+ tag_version = [cleaned_tag, cleaned_name].compact.reject(&:empty?).
137
+ select { |nm| version_class.correct?(nm) }.
138
+ map { |nm| version_class.new(nm) }.min
139
+
140
+ next conservative unless tag_version
141
+
142
+ # Reject any releases that are greater than the updated version
143
+ # (e.g., if two major versions are being maintained)
144
+ tag_version > updated_version
145
+ end
146
+ end
147
+
148
+ def updated_release
149
+ release_for_version(dependency.version)
150
+ end
151
+
152
+ def previous_release
153
+ release_for_version(dependency.previous_version)
154
+ end
155
+
156
+ def release_for_version(version)
157
+ return nil unless version
158
+
159
+ release_regex = version_regex(version)
160
+ # Doing two loops looks inefficient, but it ensures consistency
161
+ all_dep_releases.find { |r| release_regex.match?(r.tag_name.to_s) } ||
162
+ all_dep_releases.find { |r| release_regex.match?(r.name.to_s) }
163
+ end
164
+
165
+ def serialize_release(release)
166
+ rel = release
167
+ title = "## #{rel.name.to_s != '' ? rel.name : rel.tag_name}\n"
168
+ body = if rel.body.to_s.gsub(/\n*\z/m, "") == ""
169
+ "No release notes provided."
170
+ else
171
+ rel.body.gsub(/\n*\z/m, "")
172
+ end
173
+
174
+ release_body_includes_title?(rel) ? body : title + body
175
+ end
176
+
177
+ def release_body_includes_title?(release)
178
+ title = release.name.to_s != "" ? release.name : release.tag_name
179
+ release.body.to_s.match?(/\A\s*\#*\s*#{Regexp.quote(title)}/m)
180
+ end
181
+
182
+ def version_regex(version)
183
+ /(?:[^0-9\.]|\A)#{Regexp.escape(version || "unknown")}\z/
184
+ end
185
+
186
+ def version_class
187
+ Utils.version_class_for_package_manager(dependency.package_manager)
188
+ end
189
+
190
+ def fetch_dependency_releases
191
+ return [] unless source
192
+
193
+ case source.provider
194
+ when "github" then fetch_github_releases
195
+ when "bitbucket" then [] # Bitbucket doesn't support releases
196
+ when "gitlab" then fetch_gitlab_releases
197
+ when "azure" then [] # Azure can't list API for annotated tags
198
+ else raise "Unexpected repo provider '#{source.provider}'"
199
+ end
200
+ end
201
+
202
+ def fetch_github_releases
203
+ releases = github_client.releases(source.repo, per_page: 100)
204
+
205
+ # Remove any releases without a tag name. These are draft releases and
206
+ # aren't yet associated with a tag, so shouldn't be used.
207
+ releases = releases.reject { |r| r.tag_name.nil? }
208
+
209
+ clean_release_names =
210
+ releases.map { |r| r.tag_name.gsub(/^[^0-9\.]*/, "") }
211
+
212
+ if clean_release_names.all? { |nm| version_class.correct?(nm) }
213
+ releases.sort_by do |r|
214
+ version_class.new(r.tag_name.gsub(/^[^0-9\.]*/, ""))
215
+ end.reverse
216
+ else
217
+ releases.sort_by(&:id).reverse
218
+ end
219
+ rescue Octokit::NotFound
220
+ []
221
+ end
222
+
223
+ def fetch_gitlab_releases
224
+ releases =
225
+ gitlab_client.
226
+ tags(source.repo).
227
+ select(&:release).
228
+ sort_by { |r| r.commit.authored_date }.
229
+ reverse
230
+
231
+ releases.map do |tag|
232
+ OpenStruct.new(
233
+ name: tag.name,
234
+ tag_name: tag.release.tag_name,
235
+ body: tag.release.description,
236
+ html_url: "#{source.url}/tags/#{tag.name}"
237
+ )
238
+ end
239
+ rescue Gitlab::Error::NotFound
240
+ []
241
+ end
242
+
243
+ def gitlab_client
244
+ @gitlab_client ||= Dependabot::Clients::Gitlab.
245
+ for_gitlab_dot_com(credentials: credentials)
246
+ end
247
+
248
+ def github_client
249
+ @github_client ||= Dependabot::Clients::GithubWithRetries.
250
+ for_github_dot_com(credentials: credentials)
251
+ end
252
+ end
253
+ end
254
+ end
255
+ end