diff --git a/nuget/lib/dependabot/nuget/file_fetcher.rb b/nuget/lib/dependabot/nuget/file_fetcher.rb
index e6dd8380887..560874eabab 100644
--- a/nuget/lib/dependabot/nuget/file_fetcher.rb
+++ b/nuget/lib/dependabot/nuget/file_fetcher.rb
@@ -219,29 +219,32 @@ def find_and_fetch_with_suffix(suffix)
def nuget_config_files
return @nuget_config_files if @nuget_config_files
- @nuget_config_files = []
- candidate_paths = [*project_files.map { |f| File.dirname(f.name) }, "."].uniq
- visited_directories = Set.new
- candidate_paths.each do |dir|
- search_in_directory_and_parents(dir, visited_directories)
- end
+ @nuget_config_files = [*project_files.map do |f|
+ named_file_up_tree_from_project_file(f, "nuget.config")
+ end].compact.uniq
@nuget_config_files
end
- def search_in_directory_and_parents(dir, visited_directories)
- loop do
- break if visited_directories.include?(dir)
-
- visited_directories << dir
- file = repo_contents(dir: dir)
- .find { |f| f.name.casecmp("nuget.config").zero? }
- if file
- file = fetch_file_from_host(File.join(dir, file.name))
- file&.tap { |f| f.support_file = true }
- @nuget_config_files << file
+ def named_file_up_tree_from_project_file(project_file, expected_file_name)
+ found_expected_file = nil
+ directory_path = Pathname.new(directory)
+ full_project_dir = Pathname.new(project_file.directory).join(project_file.name).dirname
+ full_project_dir.ascend.each do |base|
+ break if found_expected_file
+
+ candidate_file_path = Pathname.new(base).join(expected_file_name).cleanpath.to_path
+ candidate_directory = Pathname.new(File.dirname(candidate_file_path))
+ relative_candidate_directory = candidate_directory.relative_path_from(directory_path)
+ candidate_file = repo_contents(dir: relative_candidate_directory).find do |f|
+ f.name.casecmp?(expected_file_name)
+ end
+ if candidate_file
+ found_expected_file = fetch_file_from_host(File.join(relative_candidate_directory,
+ candidate_file.name))
end
- dir = File.dirname(dir)
end
+
+ found_expected_file
end
def global_json
diff --git a/nuget/lib/dependabot/nuget/file_parser/project_file_parser.rb b/nuget/lib/dependabot/nuget/file_parser/project_file_parser.rb
index 700d2c5fb19..8294bd112a2 100644
--- a/nuget/lib/dependabot/nuget/file_parser/project_file_parser.rb
+++ b/nuget/lib/dependabot/nuget/file_parser/project_file_parser.rb
@@ -78,6 +78,10 @@ def target_frameworks(project_file:)
["net#{value[1..-1].delete('.')}"]
end
+ def nuget_configs
+ dependency_files.select { |f| f.name.match?(%r{(^|/)nuget\.config$}i) }
+ end
+
private
attr_reader :dependency_files, :credentials
@@ -281,7 +285,6 @@ def build_dependency(name, req, version, prop_name, project_file, dev: false)
end
def dependency_has_search_results?(dependency)
- nuget_configs = dependency_files.select { |f| f.name.casecmp?("nuget.config") }
dependency_urls = UpdateChecker::RepositoryFinder.new(
dependency: dependency,
credentials: credentials,
@@ -490,10 +493,6 @@ def packages_config_files
end
end
- def nuget_configs
- dependency_files.select { |f| f.name.match?(/nuget\.config$/i) }
- end
-
def global_json
dependency_files.find { |f| f.name.casecmp("global.json").zero? }
end
diff --git a/nuget/spec/dependabot/nuget/file_fetcher_spec.rb b/nuget/spec/dependabot/nuget/file_fetcher_spec.rb
index c23382e1951..33b2b0acc31 100644
--- a/nuget/spec/dependabot/nuget/file_fetcher_spec.rb
+++ b/nuget/spec/dependabot/nuget/file_fetcher_spec.rb
@@ -330,6 +330,40 @@
# end
end
+ context "NuGet.config can be found when starting in a subdirectory" do
+ let(:directory) { "/src/some-project/" }
+
+ before do
+ GitHubHelpers.stub_requests_for_directory(
+ ->(a, b) { stub_request(a, b) },
+ File.join(__dir__, "..", "..", "fixtures", "github", "csproj_in_subdirectory"),
+ "",
+ url,
+ "token token",
+ "gocardless",
+ "bump",
+ "main"
+ )
+ stub_request(:get, File.join(url, "src/some-project/.config?ref=sha"))
+ .with(headers: { "Authorization" => "token token" })
+ .to_return(
+ status: 404,
+ body: "{}",
+ headers: { "content-type" => "application/json" }
+ )
+ end
+
+ it "fetches the NuGet.config file from several directories up" do
+ expect(file_fetcher_instance.files.map(&:name))
+ .to match_array(
+ %w(
+ ../../NuGet.Config
+ some-project.csproj
+ )
+ )
+ end
+ end
+
context "with a dirs.proj" do
before do
GitHubHelpers.stub_requests_for_directory(
diff --git a/nuget/spec/dependabot/nuget/file_parser/project_file_parser_spec.rb b/nuget/spec/dependabot/nuget/file_parser/project_file_parser_spec.rb
index 8a9c31d61a3..24087864e2d 100644
--- a/nuget/spec/dependabot/nuget/file_parser/project_file_parser_spec.rb
+++ b/nuget/spec/dependabot/nuget/file_parser/project_file_parser_spec.rb
@@ -831,6 +831,42 @@ def dependencies_from(dep_info)
end
end
+ describe "nuget.config files further up the tree are considered" do
+ let(:file_body) { "not relevant" }
+ let(:file) do
+ Dependabot::DependencyFile.new(directory: "src/project", name: "my.csproj", content: file_body)
+ end
+ let(:nuget_config_body) { "not relevant" }
+ let(:nuget_config_file) do
+ Dependabot::DependencyFile.new(name: "../../NuGet.Config", content: nuget_config_body)
+ end
+ let(:parser) { described_class.new(dependency_files: [file, nuget_config_file], credentials: credentials) }
+
+ it "finds the config file up several directories" do
+ nuget_configs = parser.nuget_configs
+ expect(nuget_configs.count).to eq(1)
+ expect(nuget_configs.first).to be_a(Dependabot::DependencyFile)
+ expect(nuget_configs.first.name).to eq("../../NuGet.Config")
+ end
+ end
+
+ describe "files with a `nuget.config` suffix are not considered" do
+ let(:file_body) { "not relevant" }
+ let(:file) do
+ Dependabot::DependencyFile.new(directory: "src/project", name: "my.csproj", content: file_body)
+ end
+ let(:nuget_config_body) { "not relevant" }
+ let(:nuget_config_file) do
+ Dependabot::DependencyFile.new(name: "../../not-NuGet.Config", content: nuget_config_body)
+ end
+ let(:parser) { described_class.new(dependency_files: [file, nuget_config_file], credentials: credentials) }
+
+ it "does not return a name with a partial match" do
+ nuget_configs = parser.nuget_configs
+ expect(nuget_configs.count).to eq(0)
+ end
+ end
+
describe "multiple dependencies, but each search URI is only hit once" do
let(:file_body) do
<<~XML
diff --git a/nuget/spec/fixtures/github/csproj_in_subdirectory/NuGet.Config b/nuget/spec/fixtures/github/csproj_in_subdirectory/NuGet.Config
new file mode 100644
index 00000000000..385fba96bd0
--- /dev/null
+++ b/nuget/spec/fixtures/github/csproj_in_subdirectory/NuGet.Config
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/nuget/spec/fixtures/github/csproj_in_subdirectory/src/some-project/some-project.csproj b/nuget/spec/fixtures/github/csproj_in_subdirectory/src/some-project/some-project.csproj
new file mode 100644
index 00000000000..58990cd569d
--- /dev/null
+++ b/nuget/spec/fixtures/github/csproj_in_subdirectory/src/some-project/some-project.csproj
@@ -0,0 +1,7 @@
+
+
+
+ net8.0
+
+
+