private async Task <Nodes> LoadNodesAsync(SubmoduleInfoResult info, CancellationToken token) { await TaskScheduler.Default; token.ThrowIfCancellationRequested(); return(FillSubmoduleTree(info)); }
/// <summary> /// Check that the repo is reverted after the test, prepared for next /// An explicit Git clean and reset will require additional time /// </summary> /// <param name="result">The existing structure, reused from the test</param> /// <returns>a Task</returns> private async Task CheckRevertedStatus(SubmoduleInfoResult result) { var changedFiles = GetStatusChangedFiles(_repo1Module); changedFiles.Should().HaveCount(0); await SubmoduleTestHelpers.UpdateSubmoduleStatusAndWaitForResultAsync(_provider, _repo1Module, changedFiles); result.AllSubmodules.All(i => i.Detailed is null).Should().BeTrue(); result.TopProject.Detailed.Should().BeNull(); }
private Nodes FillSubmoduleTree(SubmoduleInfoResult result) { var threadModule = (GitModule)result.Module; var submoduleNodes = new List <SubmoduleNode>(); // 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); return(nodes); }
private Nodes FillSubmoduleTree(SubmoduleInfoResult result) { Validates.NotNull(result.TopProject); var threadModule = (GitModule?)result.Module; Validates.NotNull(threadModule); List <SubmoduleNode> submoduleNodes = new(); // We always want to display submodules rooted from the top project. CreateSubmoduleNodes(result, threadModule, ref submoduleNodes); Nodes nodes = new(this); AddTopAndNodesToTree(ref nodes, submoduleNodes, threadModule, result); return(nodes); }
private void CreateSubmoduleNodes(SubmoduleInfoResult result, GitModule threadModule, ref List <SubmoduleNode> nodes) { // 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 = result.AllSubmodules.Select(info => info.Path).ToList(); // Add current and parent module paths var parentModule = threadModule; while (parentModule is not null) { modulePaths.Add(parentModule.WorkingDir); parentModule = parentModule.SuperprojectModule; } // Sort descending so we find the nearest outer folder first modulePaths = modulePaths.OrderByDescending(path => path).ToList(); foreach (var submoduleInfo in result.AllSubmodules) { string?superPath = GetSubmoduleSuperPath(submoduleInfo.Path); if (!Directory.Exists(superPath)) { MessageBoxes.SubmoduleDirectoryDoesNotExist(owner: null, superPath ?? submoduleInfo.Path, submoduleInfo.Text); continue; } string localPath = Path.GetDirectoryName(submoduleInfo.Path.Substring(superPath.Length)).ToPosixPath(); var isCurrent = submoduleInfo.Bold; nodes.Add(new SubmoduleNode(this, submoduleInfo, isCurrent, isCurrent ? result.CurrentSubmoduleStatus : null, localPath, superPath)); } return; string?GetSubmoduleSuperPath(string submodulePath) => modulePaths.Find(path => submodulePath != path && submodulePath.Contains(path)); }
private static SubmoduleInfoResult UpdateSubmoduleStatusAndWaitForResult(ISubmoduleStatusProvider provider, GitModule module) { SubmoduleInfoResult result = null; SemaphoreSlim onUpdateCompleteSignal = new SemaphoreSlim(0, 1); provider.UpdateSubmodulesStatus( updateStatus: false, workingDirectory: module.WorkingDir, noBranchText: string.Empty, onUpdateBegin: () => { }, onUpdateCompleteAsync: async(SubmoduleInfoResult submoduleInfoResult, CancellationToken token) => { await TaskScheduler.Default; result = submoduleInfoResult; onUpdateCompleteSignal.Release(); }); onUpdateCompleteSignal.Wait(); return(result); }
public static SubmoduleInfoResult UpdateSubmoduleStatusAndWaitForResult(ISubmoduleStatusProvider provider, GitModule module, bool updateStatus = false) { SubmoduleInfoResult result = null; provider.StatusUpdated += Provider_StatusUpdated; provider.UpdateSubmodulesStatus( updateStatus: updateStatus, workingDirectory: module.WorkingDir, noBranchText: string.Empty); AsyncTestHelper.WaitForPendingOperations(); provider.StatusUpdated -= Provider_StatusUpdated; return(result); void Provider_StatusUpdated(object sender, SubmoduleStatusEventArgs e) { result = e.Info; } }
private Nodes FillSubmoduleTree(SubmoduleInfoResult result) { var threadModule = (GitModule)result.Module; var submoduleNodes = new List <SubmoduleNode>(); // 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); } var nodes = new Nodes(this); AddNodesToTree(ref nodes, submoduleNodes, threadModule, result.TopProject); return(nodes); }
private void AddTopAndNodesToTree( ref Nodes nodes, List <SubmoduleNode> submoduleNodes, GitModule threadModule, SubmoduleInfoResult result) { // Create tree of SubmoduleFolderNode for each path directory and add input SubmoduleNodes as leaves. // Example of (SuperPath + LocalPath).ToPosixPath() for all nodes: // // C:/code/gitextensions2/Externals/conemu-inside // C:/code/gitextensions2/Externals/Git.hub // C:/code/gitextensions2/Externals/ICSharpCode.TextEditor // C:/code/gitextensions2/Externals/ICSharpCode.TextEditor/gitextensions // C:/code/gitextensions2/Externals/ICSharpCode.TextEditor/gitextensions/Externals/conemu-inside // C:/code/gitextensions2/Externals/ICSharpCode.TextEditor/gitextensions/Externals/Git.hub // C:/code/gitextensions2/Externals/ICSharpCode.TextEditor/gitextensions/Externals/ICSharpCode.TextEditor // C:/code/gitextensions2/Externals/ICSharpCode.TextEditor/gitextensions/Externals/NBug // C:/code/gitextensions2/Externals/ICSharpCode.TextEditor/gitextensions/GitExtensionsDoc // C:/code/gitextensions2/Externals/NBug // C:/code/gitextensions2/GitExtensionsDoc // // What we want to do is first remove the topModule portion, "C:/code/gitextensions2/", and // then build our tree by breaking up each path into parts, separated by '/'. // // Note that when we break up the paths, some parts are just directories, the others are submodule nodes: // // Externals / ICSharpCode.TextEditor / gitextensions / Externals / Git.hub // folder submodule submodule folder submodule // // Input 'nodes' is an array of SubmoduleNodes for all the submodules; now we need to create SubmoduleFolderNodes // and insert everything into a tree. var topModule = threadModule.GetTopModule(); // Build a mapping of top-module-relative path to node var pathToNodes = new Dictionary <string, Node>(); // Add existing SubmoduleNodes foreach (var node in submoduleNodes) { pathToNodes[GetNodeRelativePath(topModule, node)] = node; } // Create and add missing SubmoduleFolderNodes foreach (var node in submoduleNodes) { var parts = GetNodeRelativePath(topModule, node).Split(Delimiters.ForwardSlash); for (int i = 0; i < parts.Length - 1; ++i) { var path = string.Join("/", parts.Take(i + 1)); if (!pathToNodes.ContainsKey(path)) { pathToNodes[path] = new SubmoduleFolderNode(this, parts[i]); } } } // Now build the tree var rootNode = new DummyNode(); var nodesInTree = new HashSet <Node>(); foreach (var node in submoduleNodes) { Node parentNode = rootNode; var parts = GetNodeRelativePath(topModule, node).Split(Delimiters.ForwardSlash); for (int i = 0; i < parts.Length; ++i) { var path = string.Join("/", parts.Take(i + 1)); var nodeToAdd = pathToNodes[path]; // If node is not already in the tree, add it if (!nodesInTree.Contains(nodeToAdd)) { parentNode.Nodes.AddNode(nodeToAdd); nodesInTree.Add(nodeToAdd); } parentNode = nodeToAdd; } } Validates.NotNull(result.TopProject); // Add top-module node, and move children of root to it var topModuleNode = new SubmoduleNode( this, result.TopProject, result.TopProject.Bold, result.TopProject.Bold ? result.CurrentSubmoduleStatus : null, "", result.TopProject.Path); topModuleNode.Nodes.AddNodes(rootNode.Nodes); nodes.AddNode(topModuleNode); }
private void UpdateSubmodulesList() { _previousUpdateTime = DateTime.Now; // Cancel any previous async activities: _submodulesStatusCTS.Cancel(); _submodulesStatusCTS.Dispose(); _submodulesStatusCTS = new CancellationTokenSource(); RemoveSubmoduleButtons(); toolStripButtonLevelUp.DropDownItems.Add(_loading.Text); // Start gathering new submodule information asynchronously. This makes a significant difference in UI // responsiveness if there are numerous submodules (e.g. > 100). var cancelToken = _submodulesStatusCTS.Token; string thisModuleDir = Module.WorkingDir; // First task: Gather list of submodules on a background thread. var updateTask = Task.Factory.StartNew(() => { // Don't access Module directly because it's not thread-safe. Use a thread-local version: GitModule threadModule = new GitModule(thisModuleDir); SubmoduleInfoResult result = new SubmoduleInfoResult(); // Add all submodules inside the current repository: foreach (var submodule in threadModule.GetSubmodulesLocalPaths().OrderBy(submoduleName => submoduleName)) { cancelToken.ThrowIfCancellationRequested(); var name = submodule; string path = threadModule.GetSubmoduleFullPath(submodule); if (Settings.DashboardShowCurrentBranch && !GitModule.IsBareRepository(path)) name = name + " " + GetModuleBranch(path); var smi = new SubmoduleInfo { Text = name, Path = path }; result.OurSubmodules.Add(smi); GetSubmoduleStatusAsync(smi, cancelToken); } if (threadModule.SuperprojectModule != null) { GitModule supersuperproject = threadModule.FindTopProjectModule(); if (threadModule.SuperprojectModule.WorkingDir != supersuperproject.WorkingDir) { var name = Path.GetFileName(Path.GetDirectoryName(supersuperproject.WorkingDir)); string path = supersuperproject.WorkingDir; if (Settings.DashboardShowCurrentBranch && !GitModule.IsBareRepository(path)) name = name + " " + GetModuleBranch(path); result.TopProject = new SubmoduleInfo { Text = name, Path = supersuperproject.WorkingDir }; GetSubmoduleStatusAsync(result.TopProject, cancelToken); } { string name; GitModule parentModule = threadModule.SuperprojectModule; string localpath = ""; if (threadModule.SuperprojectModule.WorkingDir != supersuperproject.WorkingDir) { parentModule = supersuperproject; localpath = threadModule.SuperprojectModule.WorkingDir.Substring(supersuperproject.WorkingDir.Length); localpath = PathUtil.GetDirectoryName(localpath.ToPosixPath()); name = localpath; } else name = Path.GetFileName(Path.GetDirectoryName(supersuperproject.WorkingDir)); string path = threadModule.SuperprojectModule.WorkingDir; if (Settings.DashboardShowCurrentBranch && !GitModule.IsBareRepository(path)) name = name + " " + GetModuleBranch(path); result.Superproject = new SubmoduleInfo { Text = name, Path = threadModule.SuperprojectModule.WorkingDir }; GetSubmoduleStatusAsync(result.Superproject, cancelToken); } var submodules = supersuperproject.GetSubmodulesLocalPaths().OrderBy(submoduleName => submoduleName); if (submodules.Any()) { string localpath = threadModule.WorkingDir.Substring(supersuperproject.WorkingDir.Length); localpath = PathUtil.GetDirectoryName(localpath.ToPosixPath()); foreach (var submodule in submodules) { cancelToken.ThrowIfCancellationRequested(); var name = submodule; string path = supersuperproject.GetSubmoduleFullPath(submodule); if (Settings.DashboardShowCurrentBranch && !GitModule.IsBareRepository(path)) name = name + " " + GetModuleBranch(path); bool bold = false; if (submodule == localpath) { result.CurrentSubmoduleName = threadModule.GetCurrentSubmoduleLocalPath(); bold = true; } var smi = new SubmoduleInfo { Text = name, Path = path, Bold = bold }; result.SuperSubmodules.Add(smi); GetSubmoduleStatusAsync(smi, cancelToken); } } } return result; }, cancelToken); // Second task: Populate toolbar menu on UI thread. Note further tasks are created by // CreateSubmoduleMenuItem to update images with submodule status. updateTask.ContinueWith((task) => { if (task.Result == null) return; RemoveSubmoduleButtons(); var newItems = new List<ToolStripItem>(); task.Result.OurSubmodules.ForEach(submodule => newItems.Add(CreateSubmoduleMenuItem(submodule))); if (task.Result.OurSubmodules.Count == 0) newItems.Add(new ToolStripMenuItem(_noSubmodulesPresent.Text)); if (task.Result.Superproject != null) { newItems.Add(new ToolStripSeparator()); if (task.Result.TopProject != null) newItems.Add(CreateSubmoduleMenuItem(task.Result.TopProject, _topProjectModuleFormat.Text)); newItems.Add(CreateSubmoduleMenuItem(task.Result.Superproject, _superprojectModuleFormat.Text)); task.Result.SuperSubmodules.ForEach(submodule => newItems.Add(CreateSubmoduleMenuItem(submodule))); } newItems.Add(new ToolStripSeparator()); var mi = new ToolStripMenuItem(updateAllSubmodulesToolStripMenuItem.Text); mi.Click += UpdateAllSubmodulesToolStripMenuItemClick; newItems.Add(mi); if (task.Result.CurrentSubmoduleName != null) { var usmi = new ToolStripMenuItem(_updateCurrentSubmodule.Text); usmi.Tag = task.Result.CurrentSubmoduleName; usmi.Click += UpdateSubmoduleToolStripMenuItemClick; newItems.Add(usmi); } // Using AddRange is critical: if you used Add to add menu items one at a // time, performance would be extremely slow with many submodules (> 100). toolStripButtonLevelUp.DropDownItems.AddRange(newItems.ToArray()); _previousUpdateTime = DateTime.Now; }, cancelToken, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.FromCurrentSynchronizationContext()); }