diff --git a/GitCommands/Submodules/SubmoduleInfoResult.cs b/GitCommands/Submodules/SubmoduleInfoResult.cs index 2b8aaf43f10..f31733cc2e7 100644 --- a/GitCommands/Submodules/SubmoduleInfoResult.cs +++ b/GitCommands/Submodules/SubmoduleInfoResult.cs @@ -16,8 +16,7 @@ public class SubmoduleInfoResult public IList OurSubmodules { get; } = new List(); // List of SubmoduleInfo for all submodules under TopProject. - // Only populated if current module is a submodule (i.e. is not TopProject) - public IList SuperSubmodules { get; } = new List(); + public IList AllSubmodules { get; } = new List(); // Always set to the top-most module. [NotNull] diff --git a/GitCommands/Submodules/SubmoduleStatusProvider.cs b/GitCommands/Submodules/SubmoduleStatusProvider.cs index 90fd98ddabf..b01231bba6e 100644 --- a/GitCommands/Submodules/SubmoduleStatusProvider.cs +++ b/GitCommands/Submodules/SubmoduleStatusProvider.cs @@ -87,13 +87,12 @@ public async Task UpdateSubmodulesStructureAsync(string workingDirectory, string var result = new SubmoduleInfoResult { Module = currentModule }; // Add all submodules inside the current repository: - GetRepositorySubmodulesStructure(result, noBranchText); GetSuperProjectRepositorySubmodulesStructure(currentModule, result, noBranchText); + SetOurSubmoduleData(currentModule, result); // Prepare info for status updates _submoduleInfos[result.TopProject.Path] = result.TopProject; - var allSubmodules = result.SuperProject == null ? result.OurSubmodules : result.SuperSubmodules; - foreach (var info in allSubmodules) + foreach (var info in result.AllSubmodules) { _submoduleInfos[info.Path] = info; } @@ -170,7 +169,7 @@ private void GetRepositorySubmodulesStructure(SubmoduleInfoResult result, string } var smi = new SubmoduleInfo { Text = name, Path = path }; - result.OurSubmodules.Add(smi); + result.AllSubmodules.Add(smi); } } @@ -185,6 +184,7 @@ private void GetSuperProjectRepositorySubmodulesStructure(GitModule currentModul if (isCurrentTopProject) { SetTopProjectSubmoduleInfo(result, noBranchText, result.Module, false, isCurrentTopProject); + GetRepositorySubmodulesStructure(result, noBranchText); return; } @@ -199,7 +199,7 @@ private void GetSuperProjectRepositorySubmodulesStructure(GitModule currentModul // Set result.TopProject SetTopProjectSubmoduleInfo(result, noBranchText, topProject, isParentTopProject, isCurrentTopProject); - // Set result.CurrentSubmoduleName and populate result.SuperSubmodules + // Set result.CurrentSubmoduleName and populate result.AllSubmodules SetSubmoduleData(currentModule, result, noBranchText, topProject); } @@ -247,26 +247,45 @@ private void SetSuperProjectSubmoduleInfo(GitModule superprojectModule, Submodul private void SetSubmoduleData(GitModule currentModule, SubmoduleInfoResult result, string noBranchText, IGitModule topProject) { var submodules = topProject.GetSubmodulesLocalPaths().OrderBy(submoduleName => submoduleName).ToArray(); - if (submodules.Any()) + if (!submodules.Any()) { - string localPath = result.Module.WorkingDir.Substring(topProject.WorkingDir.Length); - localPath = Path.GetDirectoryName(localPath).ToPosixPath(); + return; + } + + string localPath = result.Module.WorkingDir.Substring(topProject.WorkingDir.Length); + localPath = Path.GetDirectoryName(localPath).ToPosixPath(); + + foreach (var submodule in submodules) + { + string path = topProject.GetSubmoduleFullPath(submodule); + string name = submodule + GetBranchNameSuffix(path, noBranchText); + + bool bold = false; + if (submodule == localPath) + { + result.CurrentSubmoduleName = currentModule.GetCurrentSubmoduleLocalPath(); + bold = true; + } + + var smi = new SubmoduleInfo { Text = name, Path = path, Bold = bold }; + result.AllSubmodules.Add(smi); + } + } - foreach (var submodule in submodules) + private void SetOurSubmoduleData(GitModule currentModule, SubmoduleInfoResult result) + { + foreach (var submodule in currentModule.GetSubmodulesLocalPaths()) + { + string path = currentModule.GetSubmoduleFullPath(submodule); + var smi = result.AllSubmodules.FirstOrDefault(i => i.Path == path); + if (smi != null) { - string path = topProject.GetSubmoduleFullPath(submodule); - string name = submodule + GetBranchNameSuffix(path, noBranchText); - - bool bold = false; - if (submodule == localPath) - { - result.CurrentSubmoduleName = currentModule.GetCurrentSubmoduleLocalPath(); - bold = true; - } - - var smi = new SubmoduleInfo { Text = name, Path = path, Bold = bold }; - result.SuperSubmodules.Add(smi); + // Just ignore the unexpected situation that smi is not already added + result.OurSubmodules.Add(smi); } + + var module = new GitModule(path); + SetOurSubmoduleData(module, result); } } diff --git a/GitUI/BranchTreePanel/RepoObjectsTree.Nodes.Submodules.cs b/GitUI/BranchTreePanel/RepoObjectsTree.Nodes.Submodules.cs index cbf37955808..ca8bb4a9c09 100644 --- a/GitUI/BranchTreePanel/RepoObjectsTree.Nodes.Submodules.cs +++ b/GitUI/BranchTreePanel/RepoObjectsTree.Nodes.Submodules.cs @@ -262,17 +262,8 @@ private Nodes FillSubmoduleTree(SubmoduleInfoResult result) var submoduleNodes = new List(); - // We always want to display submodules rooted from the top project. If the currently open project is the top project, - // OurSubmodules contains all child submodules recursively; otherwise, if we're currently in a submodule, SuperSubmodules - // contains all submodule info relative to the top project. - if (result.SuperSubmodules?.Count > 0) - { - CreateSubmoduleNodes(result.SuperSubmodules, threadModule, ref submoduleNodes); - } - else - { - CreateSubmoduleNodes(result.OurSubmodules, threadModule, ref submoduleNodes); - } + // We always want to display submodules rooted from the top project. + CreateSubmoduleNodes(result.AllSubmodules, threadModule, ref submoduleNodes); var nodes = new Nodes(this); AddNodesToTree(ref nodes, submoduleNodes, threadModule, result.TopProject); @@ -281,7 +272,7 @@ private Nodes FillSubmoduleTree(SubmoduleInfoResult result) private void CreateSubmoduleNodes(IEnumerable submodules, GitModule threadModule, ref List nodes) { - // result.OurSubmodules/SuperSubmodules contain a recursive list of submodules, but don't provide info about the super + // result.OurSubmodules/AllSubmodules contain a recursive list of submodules, but don't provide info about the super // project path. So we deduce these by substring matching paths against an ordered list of all paths. var modulePaths = submodules.Select(info => info.Path).ToList(); diff --git a/GitUI/CommandsDialogs/FormBrowse.cs b/GitUI/CommandsDialogs/FormBrowse.cs index 0a0ab9119bd..0f6da790148 100644 --- a/GitUI/CommandsDialogs/FormBrowse.cs +++ b/GitUI/CommandsDialogs/FormBrowse.cs @@ -2642,7 +2642,7 @@ private async Task PopulateToolbarAsync(SubmoduleInfoResult result, Cancellation } newItems.Add(CreateSubmoduleMenuItem(cancelToken, result.SuperProject, _superprojectModuleFormat.Text)); - newItems.AddRange(result.SuperSubmodules.Select(submodule => CreateSubmoduleMenuItem(cancelToken, submodule))); + newItems.AddRange(result.AllSubmodules.Select(submodule => CreateSubmoduleMenuItem(cancelToken, submodule))); toolStripButtonLevelUp.ToolTipText = _goToSuperProject.Text; } diff --git a/UnitTests/GitCommands.Tests/Submodules/SubmoduleStatusProviderTests.cs b/UnitTests/GitCommands.Tests/Submodules/SubmoduleStatusProviderTests.cs index 7adea30c491..26bc1f29e21 100644 --- a/UnitTests/GitCommands.Tests/Submodules/SubmoduleStatusProviderTests.cs +++ b/UnitTests/GitCommands.Tests/Submodules/SubmoduleStatusProviderTests.cs @@ -68,7 +68,7 @@ public async Task UpdateSubmoduleStructure_valid_result_for_top_module() result.SuperProject.Should().Be(null); result.CurrentSubmoduleName.Should().Be(null); result.OurSubmodules.Select(info => info.Path).Should().Contain(_repo2Module.WorkingDir, _repo3Module.WorkingDir); - result.SuperSubmodules.Should().BeEmpty(); + result.AllSubmodules.Count().Should().Be(2); result.OurSubmodules.All(i => i.Detailed == null).Should().BeTrue(); } @@ -82,7 +82,7 @@ public async Task UpdateSubmoduleStructure_valid_result_for_first_nested_submodu result.SuperProject.Should().Be(result.TopProject); result.CurrentSubmoduleName.Should().Be("repo2"); result.OurSubmodules.Select(info => info.Path).Should().ContainSingle(_repo3Module.WorkingDir); - result.SuperSubmodules.Select(info => info.Path).Should().Contain(_repo2Module.WorkingDir, _repo3Module.WorkingDir); + result.AllSubmodules.Select(info => info.Path).Should().Contain(_repo2Module.WorkingDir, _repo3Module.WorkingDir); result.OurSubmodules.All(i => i.Detailed == null).Should().BeTrue(); } @@ -96,7 +96,7 @@ public async Task UpdateSubmoduleStructure_valid_result_for_second_nested_submod result.SuperProject.Path.Should().Be(_repo2Module.WorkingDir); result.CurrentSubmoduleName.Should().Be("repo3"); result.OurSubmodules.Select(info => info.Path).Should().BeEmpty(); - result.SuperSubmodules.Select(info => info.Path).Should().Contain(_repo2Module.WorkingDir, _repo3Module.WorkingDir); + result.AllSubmodules.Select(info => info.Path).Should().Contain(_repo2Module.WorkingDir, _repo3Module.WorkingDir); result.OurSubmodules.All(i => i.Detailed == null).Should().BeTrue(); } @@ -143,7 +143,8 @@ public async Task Submodule_status_changes_for_first_nested_module() var changedFiles = GetStatusChangedFiles(currentModule); changedFiles.Should().HaveCount(0); await SubmoduleTestHelpers.UpdateSubmoduleStatusAndWaitForResultAsync(_provider, currentModule, changedFiles); - result.OurSubmodules.All(i => i.Detailed == null).Should().BeTrue(); + result.AllSubmodules.All(i => i.Detailed == null).Should().BeTrue(); + result.TopProject.Detailed.Should().BeNull(); // Make a change in repo1 _repo1.CreateFile(_repo1Module.WorkingDir, "test.txt", "test"); @@ -164,9 +165,10 @@ public async Task Submodule_status_changes_for_first_nested_module() changedFiles = GetStatusChangedFiles(currentModule); changedFiles.Should().HaveCount(1); await SubmoduleTestHelpers.UpdateSubmoduleStatusAndWaitForResultAsync(_provider, currentModule, changedFiles); - - // Fails, for same reason as previous test - ////result.OurSubmodules[0].Detailed.IsDirty.Should().BeTrue(); + result.OurSubmodules[0].Detailed.IsDirty.Should().BeTrue(); + result.AllSubmodules[0].Detailed.IsDirty.Should().BeTrue(); + result.AllSubmodules[1].Detailed.IsDirty.Should().BeTrue(); + result.TopProject.Detailed.IsDirty.Should().BeTrue(); // Revert the change File.Delete(Path.Combine(_repo3Module.WorkingDir, "test.txt")); @@ -174,6 +176,9 @@ public async Task Submodule_status_changes_for_first_nested_module() changedFiles.Should().HaveCount(0); await SubmoduleTestHelpers.UpdateSubmoduleStatusAndWaitForResultAsync(_provider, currentModule, changedFiles); result.OurSubmodules.All(i => i.Detailed == null).Should().BeTrue(); + + // TopProject is unchanged (until refresh) + result.TopProject.Detailed.IsDirty.Should().BeTrue(); } private static IReadOnlyList GetStatusChangedFiles(IGitModule module)