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