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