From 8ad3b9e527f69709e02e8ed4c38f68fedaa608da Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Dec 2025 21:42:14 +0000 Subject: [PATCH 1/2] Fix duplicate solution folder when adding multiple projects from same directory - Added check in GenerateIntermediateSolutionFoldersForProjectPath to reuse existing solution folder instead of creating duplicate - Added test WhenMultipleProjectsFromSameDirectoryAreAddedSolutionFolderIsNotDuplicated to verify the fix - All existing tests pass (214 passed, 2 skipped) Co-authored-by: marcpopMSFT <12663534+marcpopMSFT@users.noreply.github.com> --- .../Solution/Add/SolutionAddCommand.cs | 13 ++++-- .../Solution/Add/GivenDotnetSlnAdd.cs | 40 +++++++++++++++++++ 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/Cli/dotnet/Commands/Solution/Add/SolutionAddCommand.cs b/src/Cli/dotnet/Commands/Solution/Add/SolutionAddCommand.cs index 116e0f5c1ad7..feec775e94c7 100644 --- a/src/Cli/dotnet/Commands/Solution/Add/SolutionAddCommand.cs +++ b/src/Cli/dotnet/Commands/Solution/Add/SolutionAddCommand.cs @@ -103,9 +103,16 @@ public override int Execute() relativeSolutionFolderPath = _solutionFolderPath; } - return string.IsNullOrEmpty(relativeSolutionFolderPath) - ? null - : solution.AddFolder(GetSolutionFolderPathWithForwardSlashes(relativeSolutionFolderPath)); + if (string.IsNullOrEmpty(relativeSolutionFolderPath)) + { + return null; + } + + // Check if a solution folder with this path already exists + var solutionFolderPath = GetSolutionFolderPathWithForwardSlashes(relativeSolutionFolderPath); + var existingFolder = solution.SolutionFolders.FirstOrDefault(f => f.Path == solutionFolderPath); + + return existingFolder ?? solution.AddFolder(solutionFolderPath); } private async Task AddProjectsToSolutionAsync(IEnumerable projectPaths, CancellationToken cancellationToken) diff --git a/test/dotnet.Tests/CommandTests/Solution/Add/GivenDotnetSlnAdd.cs b/test/dotnet.Tests/CommandTests/Solution/Add/GivenDotnetSlnAdd.cs index 3f78a0896e87..96fd06ebf7c1 100644 --- a/test/dotnet.Tests/CommandTests/Solution/Add/GivenDotnetSlnAdd.cs +++ b/test/dotnet.Tests/CommandTests/Solution/Add/GivenDotnetSlnAdd.cs @@ -409,6 +409,46 @@ public void WhenDirectoryContainsMultipleProjectsItCancelsWholeOperation(string .Should().BeVisuallyEquivalentTo(contentBefore); } + [Theory] + [InlineData("sln", ".sln")] + [InlineData("solution", ".sln")] + [InlineData("sln", ".slnx")] + [InlineData("solution", ".slnx")] + public async Task WhenMultipleProjectsFromSameDirectoryAreAddedSolutionFolderIsNotDuplicated(string solutionCommand, string solutionExtension) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndCsprojFiles", identifier: $"GivenDotnetSlnAdd-{solutionCommand}{solutionExtension}") + .WithSource() + .Path; + + var firstProject = Path.Combine("Multiple", "First.csproj"); + var secondProject = Path.Combine("Multiple", "Second.csproj"); + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, $"App{solutionExtension}", "add", firstProject, secondProject); + cmd.Should().Pass(); + + ISolutionSerializer serializer = SolutionSerializers.GetSerializerByMoniker(Path.Combine(projectDirectory, $"App{solutionExtension}")); + SolutionModel solution = await serializer.OpenAsync(Path.Combine(projectDirectory, $"App{solutionExtension}"), CancellationToken.None); + + // The solution already has App project, plus we added First and Second = 3 total + var projectsInSolution = solution.SolutionProjects.ToList(); + projectsInSolution.Count.Should().Be(3); + projectsInSolution.Should().Contain(p => p.FilePath.Contains("First.csproj")); + projectsInSolution.Should().Contain(p => p.FilePath.Contains("Second.csproj")); + + // Should only have one solution folder for "Multiple", not two + var solutionFolders = solution.SolutionFolders.ToList(); + solutionFolders.Count.Should().Be(1); + solutionFolders.Single().Path.Should().Contain("Multiple"); + + // Both new projects should be in the same solution folder + var solutionFolder = solutionFolders.Single(); + var multipleProjects = projectsInSolution.Where(p => p.FilePath.Contains("Multiple")).ToList(); + multipleProjects.Count.Should().Be(2); + multipleProjects.All(p => p.Parent?.Id == solutionFolder.Id).Should().BeTrue(); + } + [Theory] [InlineData("sln", ".sln")] [InlineData("solution", ".sln")] From 37d9724b1257b5c436e2fd9423f6d5329414cc1a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Dec 2025 21:52:32 +0000 Subject: [PATCH 2/2] Address code review feedback: use SingleOrDefault instead of FirstOrDefault - Changed to SingleOrDefault to make the expectation that solution folder paths are unique more explicit - Added comment explaining that solution folder paths should be unique - All tests still pass (4/4 for new test, 214/216 for GivenDotnetSlnAdd suite) Co-authored-by: marcpopMSFT <12663534+marcpopMSFT@users.noreply.github.com> --- src/Cli/dotnet/Commands/Solution/Add/SolutionAddCommand.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Cli/dotnet/Commands/Solution/Add/SolutionAddCommand.cs b/src/Cli/dotnet/Commands/Solution/Add/SolutionAddCommand.cs index feec775e94c7..ee372bd0b269 100644 --- a/src/Cli/dotnet/Commands/Solution/Add/SolutionAddCommand.cs +++ b/src/Cli/dotnet/Commands/Solution/Add/SolutionAddCommand.cs @@ -109,8 +109,9 @@ public override int Execute() } // Check if a solution folder with this path already exists + // Solution folder paths should be unique, so use SingleOrDefault var solutionFolderPath = GetSolutionFolderPathWithForwardSlashes(relativeSolutionFolderPath); - var existingFolder = solution.SolutionFolders.FirstOrDefault(f => f.Path == solutionFolderPath); + var existingFolder = solution.SolutionFolders.SingleOrDefault(f => f.Path == solutionFolderPath); return existingFolder ?? solution.AddFolder(solutionFolderPath); }