dependabot-core 0.94.13 → 0.95.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- metadata +13 -337
- data/CHANGELOG.md +0 -7079
- data/LICENSE +0 -39
- data/README.md +0 -114
- data/helpers/test/run.rb +0 -18
- data/helpers/utils/git-credential-store-immutable +0 -10
- data/lib/dependabot/clients/bitbucket.rb +0 -105
- data/lib/dependabot/clients/github_with_retries.rb +0 -121
- data/lib/dependabot/clients/gitlab.rb +0 -72
- data/lib/dependabot/dependency.rb +0 -115
- data/lib/dependabot/dependency_file.rb +0 -60
- data/lib/dependabot/errors.rb +0 -179
- data/lib/dependabot/file_fetchers/README.md +0 -65
- data/lib/dependabot/file_fetchers/base.rb +0 -368
- data/lib/dependabot/file_fetchers.rb +0 -18
- data/lib/dependabot/file_parsers/README.md +0 -45
- data/lib/dependabot/file_parsers/base/dependency_set.rb +0 -77
- data/lib/dependabot/file_parsers/base.rb +0 -31
- data/lib/dependabot/file_parsers.rb +0 -18
- data/lib/dependabot/file_updaters/README.md +0 -58
- data/lib/dependabot/file_updaters/base.rb +0 -52
- data/lib/dependabot/file_updaters.rb +0 -18
- data/lib/dependabot/git_commit_checker.rb +0 -412
- data/lib/dependabot/metadata_finders/README.md +0 -53
- data/lib/dependabot/metadata_finders/base/changelog_finder.rb +0 -321
- data/lib/dependabot/metadata_finders/base/changelog_pruner.rb +0 -177
- data/lib/dependabot/metadata_finders/base/commits_finder.rb +0 -221
- data/lib/dependabot/metadata_finders/base/release_finder.rb +0 -255
- data/lib/dependabot/metadata_finders/base.rb +0 -117
- data/lib/dependabot/metadata_finders.rb +0 -18
- data/lib/dependabot/pull_request_creator/branch_namer.rb +0 -170
- data/lib/dependabot/pull_request_creator/commit_signer.rb +0 -63
- data/lib/dependabot/pull_request_creator/github.rb +0 -277
- data/lib/dependabot/pull_request_creator/gitlab.rb +0 -136
- data/lib/dependabot/pull_request_creator/labeler.rb +0 -373
- data/lib/dependabot/pull_request_creator/message_builder.rb +0 -906
- data/lib/dependabot/pull_request_creator.rb +0 -153
- data/lib/dependabot/pull_request_updater/github.rb +0 -165
- data/lib/dependabot/pull_request_updater.rb +0 -43
- data/lib/dependabot/shared_helpers.rb +0 -224
- data/lib/dependabot/source.rb +0 -120
- data/lib/dependabot/update_checkers/README.md +0 -67
- data/lib/dependabot/update_checkers/base.rb +0 -220
- data/lib/dependabot/update_checkers.rb +0 -18
- data/lib/dependabot/utils.rb +0 -33
- data/lib/dependabot/version.rb +0 -5
- data/lib/dependabot.rb +0 -4
- data/lib/rubygems_version_patch.rb +0 -14
data/lib/dependabot/errors.rb
DELETED
@@ -1,179 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "dependabot/shared_helpers"
|
4
|
-
|
5
|
-
module Dependabot
|
6
|
-
class DependabotError < StandardError
|
7
|
-
def initialize(msg = nil)
|
8
|
-
msg = sanitize_message(msg)
|
9
|
-
super(msg)
|
10
|
-
end
|
11
|
-
|
12
|
-
private
|
13
|
-
|
14
|
-
def sanitize_message(message)
|
15
|
-
return unless message
|
16
|
-
|
17
|
-
path_regex =
|
18
|
-
Regexp.escape(SharedHelpers::BUMP_TMP_DIR_PATH) + "\/" +
|
19
|
-
Regexp.escape(SharedHelpers::BUMP_TMP_FILE_PREFIX) + "[^/]*"
|
20
|
-
|
21
|
-
message.gsub(/#{path_regex}/, "dependabot_tmp_dir")
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
class OutOfMemory < DependabotError; end
|
26
|
-
|
27
|
-
#####################
|
28
|
-
# Repo leval errors #
|
29
|
-
#####################
|
30
|
-
|
31
|
-
class BranchNotFound < DependabotError
|
32
|
-
attr_reader :branch_name
|
33
|
-
|
34
|
-
def initialize(branch_name, msg = nil)
|
35
|
-
@branch_name = branch_name
|
36
|
-
msg = sanitize_message(msg)
|
37
|
-
super(msg)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
class RepoNotFound < DependabotError
|
42
|
-
attr_reader :source
|
43
|
-
|
44
|
-
def initialize(source, msg = nil)
|
45
|
-
@source = source
|
46
|
-
super(msg)
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
#####################
|
51
|
-
# File level errors #
|
52
|
-
#####################
|
53
|
-
|
54
|
-
class DependencyFileNotFound < DependabotError
|
55
|
-
attr_reader :file_path
|
56
|
-
|
57
|
-
def initialize(file_path, msg = nil)
|
58
|
-
@file_path = file_path
|
59
|
-
super(msg)
|
60
|
-
end
|
61
|
-
|
62
|
-
def file_name
|
63
|
-
file_path.split("/").last
|
64
|
-
end
|
65
|
-
|
66
|
-
def directory
|
67
|
-
# Directory should always start with a `/`
|
68
|
-
file_path.split("/")[0..-2].join("/").sub(%r{^/*}, "/")
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
class DependencyFileNotParseable < DependabotError
|
73
|
-
attr_reader :file_path
|
74
|
-
|
75
|
-
def initialize(file_path, msg = nil)
|
76
|
-
@file_path = file_path
|
77
|
-
super(msg)
|
78
|
-
end
|
79
|
-
|
80
|
-
def file_name
|
81
|
-
file_path.split("/").last
|
82
|
-
end
|
83
|
-
|
84
|
-
def directory
|
85
|
-
# Directory should always start with a `/`
|
86
|
-
file_path.split("/")[0..-2].join("/").sub(%r{^/*}, "/")
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
class DependencyFileNotEvaluatable < DependabotError; end
|
91
|
-
class DependencyFileNotResolvable < DependabotError; end
|
92
|
-
|
93
|
-
#######################
|
94
|
-
# Source level errors #
|
95
|
-
#######################
|
96
|
-
|
97
|
-
class PrivateSourceAuthenticationFailure < DependabotError
|
98
|
-
attr_reader :source
|
99
|
-
|
100
|
-
def initialize(source)
|
101
|
-
@source = source
|
102
|
-
msg = "The following source could not be reached as it requires "\
|
103
|
-
"authentication (and any provided details were invalid or lacked "\
|
104
|
-
"the required permissions): #{source}"
|
105
|
-
super(msg)
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
class PrivateSourceTimedOut < DependabotError
|
110
|
-
attr_reader :source
|
111
|
-
|
112
|
-
def initialize(source)
|
113
|
-
@source = source
|
114
|
-
super("The following source timed out: #{source}")
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
class PrivateSourceCertificateFailure < DependabotError
|
119
|
-
attr_reader :source
|
120
|
-
|
121
|
-
def initialize(source)
|
122
|
-
@source = source
|
123
|
-
super("Could not verify the SSL certificate for #{source}")
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
class MissingEnvironmentVariable < DependabotError
|
128
|
-
attr_reader :environment_variable
|
129
|
-
|
130
|
-
def initialize(environment_variable)
|
131
|
-
@environment_variable = environment_variable
|
132
|
-
super("Missing environment variable #{environment_variable}")
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
# Useful for JS file updaters, where the registry API sometimes returns
|
137
|
-
# different results to the actual update process
|
138
|
-
class InconsistentRegistryResponse < DependabotError; end
|
139
|
-
|
140
|
-
###########################
|
141
|
-
# Dependency level errors #
|
142
|
-
###########################
|
143
|
-
|
144
|
-
class GitDependenciesNotReachable < DependabotError
|
145
|
-
attr_reader :dependency_urls
|
146
|
-
|
147
|
-
def initialize(*dependency_urls)
|
148
|
-
@dependency_urls =
|
149
|
-
dependency_urls.flatten.map { |uri| uri.gsub(/x-access-token.*?@/, "") }
|
150
|
-
|
151
|
-
msg = "The following git URLs could not be retrieved: "\
|
152
|
-
"#{dependency_urls.join(', ')}"
|
153
|
-
super(msg)
|
154
|
-
end
|
155
|
-
end
|
156
|
-
|
157
|
-
class GitDependencyReferenceNotFound < DependabotError
|
158
|
-
attr_reader :dependency
|
159
|
-
|
160
|
-
def initialize(dependency)
|
161
|
-
@dependency = dependency
|
162
|
-
|
163
|
-
msg = "The branch or reference specified for #{dependency} could not "\
|
164
|
-
"be retrieved"
|
165
|
-
super(msg)
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
class PathDependenciesNotReachable < DependabotError
|
170
|
-
attr_reader :dependencies
|
171
|
-
|
172
|
-
def initialize(*dependencies)
|
173
|
-
@dependencies = dependencies.flatten
|
174
|
-
msg = "The following path based dependencies could not be retrieved: "\
|
175
|
-
"#{dependencies.join(', ')}"
|
176
|
-
super(msg)
|
177
|
-
end
|
178
|
-
end
|
179
|
-
end
|
@@ -1,65 +0,0 @@
|
|
1
|
-
# File fetchers
|
2
|
-
|
3
|
-
File fetchers are used to fetch the relevant dependency files for a project
|
4
|
-
(e.g., the `Gemfile` and `Gemfile.lock`). They are also responsible for checking
|
5
|
-
whether a repo has an admissable set of requirement files.
|
6
|
-
|
7
|
-
There is a `Dependabot::FileFetchers` class for each language Dependabot
|
8
|
-
supports.
|
9
|
-
|
10
|
-
## Public API
|
11
|
-
|
12
|
-
Each `Dependabot::FileFetchers` class implements the following methods:
|
13
|
-
|
14
|
-
| Method | Description |
|
15
|
-
|----------------------------------|-----------------------------------------------------------------------------------------------|
|
16
|
-
| `.required_files_in?` | Checks an array of filenames (string) and returns a boolean describing whether the language-specific dependency files required for an update run are present. |
|
17
|
-
| `.required_files_message` | Returns a static error message which can be displayed to a user if `required_files_in?` returns false. |
|
18
|
-
| `#files` | Fetches the language-specific dependency files for the repo this instance was created with. Returns an array of `Dependabot::DependencyFile` instances. |
|
19
|
-
| `#commit` | Returns the commit SHA-1 hash at the time the dependency files were fetched. If called before `files`, the returned value will be used in subsequent calls to `files`. |
|
20
|
-
|
21
|
-
|
22
|
-
An integration might look as follows:
|
23
|
-
|
24
|
-
```ruby
|
25
|
-
require 'octokit'
|
26
|
-
require 'dependabot/file_fetchers'
|
27
|
-
require 'dependabot/source'
|
28
|
-
|
29
|
-
target_repo_name = 'dependabot/dependabot-core'
|
30
|
-
source = Dependabot::Source.new(provider: 'github', repo: target_repo_name)
|
31
|
-
|
32
|
-
client = Octokit::Client.new
|
33
|
-
fetcher_class = Dependabot::FileFetchers::Ruby::Bundler
|
34
|
-
filenames = client.contents(target_repo_name).map(&:name)
|
35
|
-
|
36
|
-
unless fetcher_class.required_files_in?(filenames)
|
37
|
-
raise fetcher_class.required_files_message
|
38
|
-
end
|
39
|
-
|
40
|
-
fetcher = fetcher_class.new(source: source, credentials: [])
|
41
|
-
|
42
|
-
puts "Fetched #{fetcher.files.map(&:name)}, at commit SHA-1 '#{fetcher.commit}'"
|
43
|
-
```
|
44
|
-
|
45
|
-
## Writing a file fetcher for a new language
|
46
|
-
|
47
|
-
All new file fetchers should inherit from `Dependabot::FileFetchers::Base` and
|
48
|
-
implement the following methods:
|
49
|
-
|
50
|
-
| Method | Description |
|
51
|
-
|----------------------------------|-----------------------------------------------------------------------------------------------|
|
52
|
-
| `.required_files_in?` | See Public API section. |
|
53
|
-
| `.required_files_message` | See Public API section. |
|
54
|
-
| `#fetch_files` | Private method to fetch the required files from GitHub. For each required file, you can use the `fetch_file_from_host(filename)` method from `Dependabot::FileFetchers::Base` to do the fetching. |
|
55
|
-
|
56
|
-
To ensure the above are implemented, you should include
|
57
|
-
`it_behaves_like "a dependency file fetcher"` in your specs for the new file
|
58
|
-
fetcher.
|
59
|
-
|
60
|
-
File fetchers tend to get complicated when the file requirements for an update
|
61
|
-
to run are non-trivial - for example, for Ruby we could accept
|
62
|
-
[`Gemfile`, `Gemfile.lock`] or [`Gemfile`, `example.gemspec`],
|
63
|
-
but not just [`Gemfile.lock`]. When adding a new lanugage, it's normally easiest
|
64
|
-
to pick a single case and implement it for all the update steps (parsing, update
|
65
|
-
checking, etc.). You can then return and add other cases later.
|
@@ -1,368 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "dependabot/dependency_file"
|
4
|
-
require "dependabot/source"
|
5
|
-
require "dependabot/errors"
|
6
|
-
require "dependabot/clients/github_with_retries"
|
7
|
-
require "dependabot/clients/bitbucket"
|
8
|
-
require "dependabot/clients/gitlab"
|
9
|
-
require "dependabot/shared_helpers"
|
10
|
-
|
11
|
-
# rubocop:disable Metrics/ClassLength
|
12
|
-
module Dependabot
|
13
|
-
module FileFetchers
|
14
|
-
class Base
|
15
|
-
attr_reader :source, :credentials
|
16
|
-
|
17
|
-
CLIENT_NOT_FOUND_ERRORS = [
|
18
|
-
Octokit::NotFound,
|
19
|
-
Gitlab::Error::NotFound,
|
20
|
-
Dependabot::Clients::Bitbucket::NotFound
|
21
|
-
].freeze
|
22
|
-
|
23
|
-
def self.required_files_in?(_filename_array)
|
24
|
-
raise NotImplementedError
|
25
|
-
end
|
26
|
-
|
27
|
-
def self.required_files_message
|
28
|
-
raise NotImplementedError
|
29
|
-
end
|
30
|
-
|
31
|
-
def initialize(source:, credentials:)
|
32
|
-
@source = source
|
33
|
-
@credentials = credentials
|
34
|
-
|
35
|
-
@submodule_directories = {}
|
36
|
-
end
|
37
|
-
|
38
|
-
def repo
|
39
|
-
source.repo
|
40
|
-
end
|
41
|
-
|
42
|
-
def directory
|
43
|
-
source.directory || "/"
|
44
|
-
end
|
45
|
-
|
46
|
-
def target_branch
|
47
|
-
source.branch
|
48
|
-
end
|
49
|
-
|
50
|
-
def files
|
51
|
-
@files ||= fetch_files
|
52
|
-
end
|
53
|
-
|
54
|
-
def commit
|
55
|
-
branch = target_branch || default_branch_for_repo
|
56
|
-
|
57
|
-
@commit ||= client_for_provider.fetch_commit(repo, branch)
|
58
|
-
rescue *CLIENT_NOT_FOUND_ERRORS
|
59
|
-
raise Dependabot::BranchNotFound, branch
|
60
|
-
rescue Octokit::Conflict => error
|
61
|
-
raise unless error.message.include?("Repository is empty")
|
62
|
-
end
|
63
|
-
|
64
|
-
private
|
65
|
-
|
66
|
-
def fetch_file_if_present(filename, fetch_submodules: false)
|
67
|
-
dir = File.dirname(filename)
|
68
|
-
basename = File.basename(filename)
|
69
|
-
|
70
|
-
repo_includes_basename =
|
71
|
-
repo_contents(dir: dir, fetch_submodules: fetch_submodules).
|
72
|
-
reject { |f| f.type == "dir" }.
|
73
|
-
map(&:name).include?(basename)
|
74
|
-
return unless repo_includes_basename
|
75
|
-
|
76
|
-
fetch_file_from_host(filename, fetch_submodules: fetch_submodules)
|
77
|
-
rescue *CLIENT_NOT_FOUND_ERRORS
|
78
|
-
path = Pathname.new(File.join(directory, filename)).cleanpath.to_path
|
79
|
-
raise Dependabot::DependencyFileNotFound, path
|
80
|
-
end
|
81
|
-
|
82
|
-
def fetch_file_from_host(filename, type: "file", fetch_submodules: false)
|
83
|
-
path = Pathname.new(File.join(directory, filename)).cleanpath.to_path
|
84
|
-
|
85
|
-
DependencyFile.new(
|
86
|
-
name: Pathname.new(filename).cleanpath.to_path,
|
87
|
-
directory: directory,
|
88
|
-
type: type,
|
89
|
-
content: _fetch_file_content(path, fetch_submodules: fetch_submodules)
|
90
|
-
)
|
91
|
-
rescue *CLIENT_NOT_FOUND_ERRORS
|
92
|
-
raise Dependabot::DependencyFileNotFound, path
|
93
|
-
end
|
94
|
-
|
95
|
-
def repo_contents(dir: ".", ignore_base_directory: false,
|
96
|
-
raise_errors: true, fetch_submodules: false)
|
97
|
-
dir = File.join(directory, dir) unless ignore_base_directory
|
98
|
-
path = Pathname.new(File.join(dir)).cleanpath.to_path.gsub(%r{^/*}, "")
|
99
|
-
|
100
|
-
@repo_contents ||= {}
|
101
|
-
@repo_contents[dir] ||= _fetch_repo_contents(
|
102
|
-
path,
|
103
|
-
raise_errors: raise_errors,
|
104
|
-
fetch_submodules: fetch_submodules
|
105
|
-
)
|
106
|
-
end
|
107
|
-
|
108
|
-
#################################################
|
109
|
-
# INTERNAL METHODS (not for use by sub-classes) #
|
110
|
-
#################################################
|
111
|
-
|
112
|
-
def _fetch_repo_contents(path, fetch_submodules: false,
|
113
|
-
raise_errors: true)
|
114
|
-
path = path.gsub(" ", "%20")
|
115
|
-
provider, repo, tmp_path, commit =
|
116
|
-
_full_specification_for(path, fetch_submodules: fetch_submodules).
|
117
|
-
values_at(:provider, :repo, :path, :commit)
|
118
|
-
|
119
|
-
_fetch_repo_contents_fully_specified(provider, repo, tmp_path, commit)
|
120
|
-
rescue *CLIENT_NOT_FOUND_ERRORS
|
121
|
-
result = raise_errors ? -> { raise } : -> { [] }
|
122
|
-
retrying ||= false
|
123
|
-
|
124
|
-
# If the path changes after calling _fetch_repo_contents_fully_specified
|
125
|
-
# it's because we've found a sub-module (and are fetching them). Trigger
|
126
|
-
# a retry to get its contents.
|
127
|
-
updated_path =
|
128
|
-
_full_specification_for(path, fetch_submodules: fetch_submodules).
|
129
|
-
fetch(:path)
|
130
|
-
retry if updated_path != tmp_path
|
131
|
-
|
132
|
-
return result.call unless fetch_submodules && !retrying
|
133
|
-
|
134
|
-
_find_submodules(path)
|
135
|
-
return result.call unless _submodule_for(path)
|
136
|
-
|
137
|
-
retrying = true
|
138
|
-
retry
|
139
|
-
end
|
140
|
-
|
141
|
-
def _fetch_repo_contents_fully_specified(provider, repo, path, commit)
|
142
|
-
case provider
|
143
|
-
when "github"
|
144
|
-
_github_repo_contents(repo, path, commit)
|
145
|
-
when "gitlab"
|
146
|
-
_gitlab_repo_contents(repo, path, commit)
|
147
|
-
when "bitbucket"
|
148
|
-
_bitbucket_repo_contents(repo, path, commit)
|
149
|
-
else raise "Unsupported provider '#{provider}'."
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
def _github_repo_contents(repo, path, commit)
|
154
|
-
path = path.gsub(" ", "%20")
|
155
|
-
github_response = github_client.contents(repo, path: path, ref: commit)
|
156
|
-
|
157
|
-
if github_response.respond_to?(:type) &&
|
158
|
-
github_response.type == "submodule"
|
159
|
-
@submodule_directories[path] = github_response
|
160
|
-
raise Octokit::NotFound
|
161
|
-
elsif github_response.respond_to?(:type)
|
162
|
-
raise Octokit::NotFound
|
163
|
-
end
|
164
|
-
|
165
|
-
github_response.map { |f| _build_github_file_struct(f) }
|
166
|
-
end
|
167
|
-
|
168
|
-
def _build_github_file_struct(file)
|
169
|
-
OpenStruct.new(
|
170
|
-
name: file.name,
|
171
|
-
path: file.path,
|
172
|
-
type: file.type,
|
173
|
-
sha: file.sha,
|
174
|
-
size: file.size
|
175
|
-
)
|
176
|
-
end
|
177
|
-
|
178
|
-
def _gitlab_repo_contents(repo, path, commit)
|
179
|
-
gitlab_client.
|
180
|
-
repo_tree(repo, path: path, ref_name: commit, per_page: 100).
|
181
|
-
map do |file|
|
182
|
-
OpenStruct.new(
|
183
|
-
name: file.name,
|
184
|
-
path: file.path,
|
185
|
-
type: file.type == "blob" ? "file" : file.type,
|
186
|
-
size: 0 # GitLab doesn't return file size
|
187
|
-
)
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
def _bitbucket_repo_contents(repo, path, commit)
|
192
|
-
response = bitbucket_client.fetch_repo_contents(
|
193
|
-
repo,
|
194
|
-
commit,
|
195
|
-
path
|
196
|
-
)
|
197
|
-
|
198
|
-
response.map do |file|
|
199
|
-
type = case file.fetch("type")
|
200
|
-
when "commit_file" then "file"
|
201
|
-
when "commit_directory" then "dir"
|
202
|
-
else file.fetch("type")
|
203
|
-
end
|
204
|
-
|
205
|
-
OpenStruct.new(
|
206
|
-
name: File.basename(file.fetch("path")),
|
207
|
-
path: file.fetch("path"),
|
208
|
-
type: type,
|
209
|
-
size: file.fetch("size", 0)
|
210
|
-
)
|
211
|
-
end
|
212
|
-
end
|
213
|
-
|
214
|
-
def _full_specification_for(path, fetch_submodules:)
|
215
|
-
if fetch_submodules && _submodule_for(path) &&
|
216
|
-
Source.from_url(
|
217
|
-
@submodule_directories[_submodule_for(path)].submodule_git_url
|
218
|
-
)
|
219
|
-
submodule_details = @submodule_directories[_submodule_for(path)]
|
220
|
-
sub_source = Source.from_url(submodule_details.submodule_git_url)
|
221
|
-
{
|
222
|
-
repo: sub_source.repo,
|
223
|
-
commit: submodule_details.sha,
|
224
|
-
provider: sub_source.provider,
|
225
|
-
path: path.gsub(%r{^#{Regexp.quote(_submodule_for(path))}(/|$)}, "")
|
226
|
-
}
|
227
|
-
else
|
228
|
-
{
|
229
|
-
repo: source.repo,
|
230
|
-
path: path,
|
231
|
-
commit: commit,
|
232
|
-
provider: source.provider
|
233
|
-
}
|
234
|
-
end
|
235
|
-
end
|
236
|
-
|
237
|
-
def _fetch_file_content(path, fetch_submodules: false)
|
238
|
-
path = path.gsub(%r{^/*}, "")
|
239
|
-
|
240
|
-
provider, repo, path, commit =
|
241
|
-
_full_specification_for(path, fetch_submodules: fetch_submodules).
|
242
|
-
values_at(:provider, :repo, :path, :commit)
|
243
|
-
|
244
|
-
_fetch_file_content_fully_specified(provider, repo, path, commit)
|
245
|
-
rescue *CLIENT_NOT_FOUND_ERRORS
|
246
|
-
retrying ||= false
|
247
|
-
|
248
|
-
raise unless fetch_submodules && !retrying && !_submodule_for(path)
|
249
|
-
|
250
|
-
_find_submodules(path)
|
251
|
-
raise unless _submodule_for(path)
|
252
|
-
|
253
|
-
retrying = true
|
254
|
-
retry
|
255
|
-
end
|
256
|
-
|
257
|
-
def _fetch_file_content_fully_specified(provider, repo, path, commit)
|
258
|
-
case provider
|
259
|
-
when "github"
|
260
|
-
_fetch_file_content_from_github(path, repo, commit)
|
261
|
-
when "gitlab"
|
262
|
-
tmp = gitlab_client.get_file(repo, path, commit).content
|
263
|
-
Base64.decode64(tmp).force_encoding("UTF-8").encode
|
264
|
-
when "bitbucket"
|
265
|
-
bitbucket_client.fetch_file_contents(repo, commit, path)
|
266
|
-
else raise "Unsupported provider '#{source.provider}'."
|
267
|
-
end
|
268
|
-
end
|
269
|
-
|
270
|
-
# rubocop:disable Metrics/AbcSize
|
271
|
-
def _fetch_file_content_from_github(path, repo, commit)
|
272
|
-
tmp = github_client.contents(repo, path: path, ref: commit)
|
273
|
-
|
274
|
-
raise Octokit::NotFound if tmp.is_a?(Array)
|
275
|
-
|
276
|
-
if tmp.type == "symlink"
|
277
|
-
tmp = github_client.contents(
|
278
|
-
repo,
|
279
|
-
path: tmp.target,
|
280
|
-
ref: commit
|
281
|
-
)
|
282
|
-
end
|
283
|
-
|
284
|
-
Base64.decode64(tmp.content).force_encoding("UTF-8").encode
|
285
|
-
rescue Octokit::Forbidden => error
|
286
|
-
raise unless error.message.include?("too_large")
|
287
|
-
|
288
|
-
# Fall back to Git Data API to fetch the file
|
289
|
-
prefix_dir = directory.gsub(%r{(^/|/$)}, "")
|
290
|
-
dir = File.dirname(path).gsub(%r{^/?#{Regexp.escape(prefix_dir)}/?}, "")
|
291
|
-
basename = File.basename(path)
|
292
|
-
file_details = repo_contents(dir: dir).find { |f| f.name == basename }
|
293
|
-
raise unless file_details
|
294
|
-
|
295
|
-
tmp = github_client.blob(repo, file_details.sha)
|
296
|
-
return tmp.content if tmp.encoding == "utf-8"
|
297
|
-
|
298
|
-
Base64.decode64(tmp.content).force_encoding("UTF-8").encode
|
299
|
-
end
|
300
|
-
# rubocop:enable Metrics/AbcSize
|
301
|
-
|
302
|
-
def default_branch_for_repo
|
303
|
-
@default_branch_for_repo ||= client_for_provider.
|
304
|
-
fetch_default_branch(repo)
|
305
|
-
rescue *CLIENT_NOT_FOUND_ERRORS
|
306
|
-
raise Dependabot::RepoNotFound, source
|
307
|
-
end
|
308
|
-
|
309
|
-
# Update the @submodule_directories hash by exploiting a side-effect of
|
310
|
-
# recursively calling `repo_contents` for each directory up the tree
|
311
|
-
# until a submodule is found
|
312
|
-
def _find_submodules(path)
|
313
|
-
path = Pathname.new(path).cleanpath.to_path.gsub(%r{^/*}, "")
|
314
|
-
dir = File.dirname(path)
|
315
|
-
|
316
|
-
return if [directory, "."].include?(dir)
|
317
|
-
|
318
|
-
repo_contents(
|
319
|
-
dir: dir,
|
320
|
-
ignore_base_directory: true,
|
321
|
-
fetch_submodules: true,
|
322
|
-
raise_errors: false
|
323
|
-
)
|
324
|
-
end
|
325
|
-
|
326
|
-
def _submodule_for(path)
|
327
|
-
submodules = @submodule_directories.keys
|
328
|
-
submodules.
|
329
|
-
select { |k| path.match?(%r{^#{Regexp.quote(k)}(/|$)}) }.
|
330
|
-
max_by(&:length)
|
331
|
-
end
|
332
|
-
|
333
|
-
def client_for_provider
|
334
|
-
case source.provider
|
335
|
-
when "github" then github_client
|
336
|
-
when "gitlab" then gitlab_client
|
337
|
-
when "bitbucket" then bitbucket_client
|
338
|
-
else raise "Unsupported provider '#{source.provider}'."
|
339
|
-
end
|
340
|
-
end
|
341
|
-
|
342
|
-
def github_client
|
343
|
-
@github_client ||=
|
344
|
-
Dependabot::Clients::GithubWithRetries.for_source(
|
345
|
-
source: source,
|
346
|
-
credentials: credentials
|
347
|
-
)
|
348
|
-
end
|
349
|
-
|
350
|
-
def gitlab_client
|
351
|
-
@gitlab_client ||=
|
352
|
-
Dependabot::Clients::Gitlab.for_source(
|
353
|
-
source: source,
|
354
|
-
credentials: credentials
|
355
|
-
)
|
356
|
-
end
|
357
|
-
|
358
|
-
def bitbucket_client
|
359
|
-
# TODO: When self-hosted Bitbucket is supported this should use
|
360
|
-
# `Bitbucket.for_source`
|
361
|
-
@bitbucket_client ||=
|
362
|
-
Dependabot::Clients::Bitbucket.
|
363
|
-
for_bitbucket_dot_org(credentials: credentials)
|
364
|
-
end
|
365
|
-
end
|
366
|
-
end
|
367
|
-
end
|
368
|
-
# rubocop:enable Metrics/ClassLength
|
@@ -1,18 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Dependabot
|
4
|
-
module FileFetchers
|
5
|
-
@file_fetchers = {}
|
6
|
-
|
7
|
-
def self.for_package_manager(package_manager)
|
8
|
-
file_fetcher = @file_fetchers[package_manager]
|
9
|
-
return file_fetcher if file_fetcher
|
10
|
-
|
11
|
-
raise "Unsupported package_manager #{package_manager}"
|
12
|
-
end
|
13
|
-
|
14
|
-
def self.register(package_manager, file_fetcher)
|
15
|
-
@file_fetchers[package_manager] = file_fetcher
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
@@ -1,45 +0,0 @@
|
|
1
|
-
# File parsers
|
2
|
-
|
3
|
-
File parsers take a set of dependency files and extract a list of dependencies
|
4
|
-
for the project.
|
5
|
-
|
6
|
-
There is a `Dependabot::FileParsers` class for each language Dependabot
|
7
|
-
supports.
|
8
|
-
|
9
|
-
## Public API
|
10
|
-
|
11
|
-
Each `Dependabot::FileParsers` class implements the following methods:
|
12
|
-
|
13
|
-
| Method | Description |
|
14
|
-
|---------------------|-----------------------------------------------------------------------------------------------|
|
15
|
-
| `#parse` | Returns an array of `Dependabot::Dependency` instances, representing the dependencies for the project. Each `Dependabot::Dependency` has a `name`, `version` and a `requirements` array |
|
16
|
-
|
17
|
-
An integration might look as follows:
|
18
|
-
|
19
|
-
```ruby
|
20
|
-
require 'dependabot/file_parsers'
|
21
|
-
|
22
|
-
files = fetcher.files
|
23
|
-
|
24
|
-
parser_class = Dependabot::FileParsers::Ruby::Bundler
|
25
|
-
source = Dependabot::Source.new(provider: 'github', repo: "gocardless/business")
|
26
|
-
parser = parser_class.new(dependency_files: files, source: source)
|
27
|
-
|
28
|
-
dependencies = parser.parse
|
29
|
-
|
30
|
-
puts "Found the following dependencies: #{dependencies.map(&:name)}"
|
31
|
-
```
|
32
|
-
|
33
|
-
## Writing a file parser for a new language
|
34
|
-
|
35
|
-
All new file parsers should inherit from `Dependabot::FileParsers::Base` and
|
36
|
-
implement the following methods:
|
37
|
-
|
38
|
-
| Method | Description |
|
39
|
-
|-------------------------|-----------------------------------------------------------------------------------------------|
|
40
|
-
| `#parse` | See Public API section. |
|
41
|
-
| `#check_required_files` | Raise a runtime error unless an appropriate set of files is provided. Private. |
|
42
|
-
|
43
|
-
To ensure the above are implemented, you should include
|
44
|
-
`it_behaves_like "a dependency file parser"` in your specs for the new file
|
45
|
-
parser.
|