diff --git a/src/App.axaml.cs b/src/App.axaml.cs index 411f5cfb1..b5868ca1d 100644 --- a/src/App.axaml.cs +++ b/src/App.axaml.cs @@ -338,7 +338,7 @@ public static IStorageProvider GetStorageProvider() return null; } - public static ViewModels.Launcher GetLauncer() + public static ViewModels.Launcher GetLauncher() { return Current is App app ? app._launcher : null; } diff --git a/src/Models/Watcher.cs b/src/Models/Watcher.cs index 928951caa..ccdc645f7 100644 --- a/src/Models/Watcher.cs +++ b/src/Models/Watcher.cs @@ -157,7 +157,7 @@ private void OnRepositoryChanged(object o, FileSystemEventArgs e) if (string.IsNullOrEmpty(e.Name)) return; - var name = e.Name.Replace("\\", "/"); + var name = e.Name.Replace('\\', '/').TrimEnd('/'); if (name.Contains("fsmonitor--daemon/", StringComparison.Ordinal) || name.EndsWith(".lock", StringComparison.Ordinal) || name.StartsWith("lfs/", StringComparison.Ordinal)) @@ -205,7 +205,7 @@ private void OnWorkingCopyChanged(object o, FileSystemEventArgs e) if (string.IsNullOrEmpty(e.Name)) return; - var name = e.Name.Replace("\\", "/"); + var name = e.Name.Replace('\\', '/').TrimEnd('/'); if (name.Equals(".git", StringComparison.Ordinal) || name.StartsWith(".git/", StringComparison.Ordinal) || name.EndsWith("/.git", StringComparison.Ordinal)) diff --git a/src/ViewModels/Blame.cs b/src/ViewModels/Blame.cs index c04842e56..d35586700 100644 --- a/src/ViewModels/Blame.cs +++ b/src/ViewModels/Blame.cs @@ -45,7 +45,7 @@ public Blame(string repo, string file, string revision) public void NavigateToCommit(string commitSHA) { - var launcher = App.GetLauncer(); + var launcher = App.GetLauncher(); if (launcher == null) return; diff --git a/src/ViewModels/BranchCompare.cs b/src/ViewModels/BranchCompare.cs index 4edb978ce..f56cfd76e 100644 --- a/src/ViewModels/BranchCompare.cs +++ b/src/ViewModels/BranchCompare.cs @@ -86,7 +86,7 @@ public BranchCompare(string repo, Models.Branch baseBranch, Models.Branch toBran public void NavigateTo(string commitSHA) { - var launcher = App.GetLauncer(); + var launcher = App.GetLauncher(); if (launcher == null) return; diff --git a/src/ViewModels/Clone.cs b/src/ViewModels/Clone.cs index 032551a26..94a74893e 100644 --- a/src/ViewModels/Clone.cs +++ b/src/ViewModels/Clone.cs @@ -150,7 +150,7 @@ public override Task Sure() CallUIThread(() => { var node = Preferences.Instance.FindOrAddNodeByRepositoryPath(path, null, true); - var launcher = App.GetLauncer(); + var launcher = App.GetLauncher(); var page = null as LauncherPage; foreach (var one in launcher.Pages) { diff --git a/src/ViewModels/DiffContext.cs b/src/ViewModels/DiffContext.cs index af0896356..aa71b19cd 100644 --- a/src/ViewModels/DiffContext.cs +++ b/src/ViewModels/DiffContext.cs @@ -133,7 +133,7 @@ private void LoadDiffContent() if (count <= 3) { var submoduleDiff = new Models.SubmoduleDiff(); - var submoduleRoot = $"{_repo}/{_option.Path}".Replace("\\", "/"); + var submoduleRoot = $"{_repo}/{_option.Path}".Replace('\\', '/').TrimEnd('/'); isSubmodule = true; for (int i = 1; i < count; i++) { diff --git a/src/ViewModels/Launcher.cs b/src/ViewModels/Launcher.cs index 4c0714df5..004b6a0a0 100644 --- a/src/ViewModels/Launcher.cs +++ b/src/ViewModels/Launcher.cs @@ -421,7 +421,7 @@ public void DispatchNotification(string pageId, string message, bool isError) foreach (var page in Pages) { - var id = page.Node.Id.Replace("\\", "/"); + var id = page.Node.Id.Replace('\\', '/').TrimEnd('/'); if (id == pageId) { page.Notifications.Add(notification); diff --git a/src/ViewModels/Preferences.cs b/src/ViewModels/Preferences.cs index df6d36bdf..cde35f3fc 100644 --- a/src/ViewModels/Preferences.cs +++ b/src/ViewModels/Preferences.cs @@ -430,16 +430,26 @@ public void AddNode(RepositoryNode node, RepositoryNode to, bool save) { var collection = to == null ? RepositoryNodes : to.SubNodes; collection.Add(node); - collection.Sort((l, r) => + SortNodes(collection); + + if (save) + Save(); + } + + public void SortNodes(List collection) + { + collection?.Sort((l, r) => { if (l.IsRepository != r.IsRepository) return l.IsRepository ? 1 : -1; - return string.Compare(l.Name, r.Name, StringComparison.Ordinal); + return string.Compare(l.Name, r.Name, StringComparison.OrdinalIgnoreCase); }); + } - if (save) - Save(); + public void SortAllNodes() + { + SortNodesRecursive(RepositoryNodes); } public RepositoryNode FindNode(string id) @@ -449,9 +459,7 @@ public RepositoryNode FindNode(string id) public RepositoryNode FindOrAddNodeByRepositoryPath(string repo, RepositoryNode parent, bool shouldMoveNode) { - var normalized = repo.Replace('\\', '/'); - if (normalized.EndsWith("/")) - normalized = normalized.TrimEnd('/'); + var normalized = repo.Replace('\\', '/').TrimEnd('/'); var node = FindNodeRecursive(normalized, RepositoryNodes); if (node == null) @@ -499,22 +507,14 @@ public void RemoveNode(RepositoryNode node, bool save) public void SortByRenamedNode(RepositoryNode node) { var container = FindNodeContainer(node, RepositoryNodes); - container?.Sort((l, r) => - { - if (l.IsRepository != r.IsRepository) - return l.IsRepository ? 1 : -1; - - return string.Compare(l.Name, r.Name, StringComparison.Ordinal); - }); + SortNodes(container); Save(); } - public void AutoRemoveInvalidNode() + public bool AutoRemoveInvalidNode() { - var changed = RemoveInvalidRepositoriesRecursive(RepositoryNodes); - if (changed) - Save(); + return RemoveInvalidRepositoriesRecursive(RepositoryNodes); } public void Save() @@ -584,6 +584,13 @@ private void PrepareWorkspaces() } } + private void SortNodesRecursive(List collection) + { + SortNodes(collection); + foreach (var node in collection) + SortNodesRecursive(node.SubNodes); + } + private RepositoryNode FindNodeRecursive(string id, List collection) { foreach (var node in collection) diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index fcc4a8530..2b2771eca 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -30,7 +30,7 @@ public string FullPath { if (value != null) { - var normalized = value.Replace('\\', '/'); + var normalized = value.Replace('\\', '/').TrimEnd('/'); SetProperty(ref _fullpath, normalized); } else @@ -499,7 +499,7 @@ public void Open() { // For worktrees, we need to watch the $GIT_COMMON_DIR instead of the $GIT_DIR. var gitDirForWatcher = _gitDir; - if (_gitDir.Replace("\\", "/").IndexOf("/worktrees/", StringComparison.Ordinal) > 0) + if (_gitDir.Replace('\\', '/').IndexOf("/worktrees/", StringComparison.Ordinal) > 0) { var commonDir = new Commands.QueryGitCommonDir(_fullpath).Result(); if (!string.IsNullOrEmpty(commonDir)) @@ -1120,7 +1120,8 @@ public void RefreshBranches() if (_workingCopy != null) _workingCopy.HasRemotes = remotes.Count > 0; - GetOwnerPage()?.ChangeDirtyState(Models.DirtyState.HasPendingPullOrPush, !CurrentBranch.TrackStatus.IsVisible); + var hasPendingPullOrPush = CurrentBranch?.TrackStatus.IsVisible ?? false; + GetOwnerPage()?.ChangeDirtyState(Models.DirtyState.HasPendingPullOrPush, !hasPendingPullOrPush); }); } @@ -1387,7 +1388,7 @@ public void OpenSubmodule(string submodule) return; var root = Path.GetFullPath(Path.Combine(_fullpath, submodule)); - var normalizedPath = root.Replace("\\", "/"); + var normalizedPath = root.Replace('\\', '/').TrimEnd('/'); var node = Preferences.Instance.FindNode(normalizedPath); if (node == null) @@ -1401,7 +1402,7 @@ public void OpenSubmodule(string submodule) }; } - App.GetLauncer().OpenRepositoryInTab(node, null); + App.GetLauncher().OpenRepositoryInTab(node, null); } public void AddWorktree() @@ -1430,7 +1431,7 @@ public void OpenWorktree(Models.Worktree worktree) }; } - App.GetLauncer()?.OpenRepositoryInTab(node, null); + App.GetLauncher()?.OpenRepositoryInTab(node, null); } public List GetPreferedOpenAIServices() @@ -2588,7 +2589,7 @@ public ContextMenu CreateContextMenuForWorktree(Models.Worktree worktree) private LauncherPage GetOwnerPage() { - var launcher = App.GetLauncer(); + var launcher = App.GetLauncher(); if (launcher == null) return null; diff --git a/src/ViewModels/RepositoryNode.cs b/src/ViewModels/RepositoryNode.cs index cd79c06e6..c65d1dbd0 100644 --- a/src/ViewModels/RepositoryNode.cs +++ b/src/ViewModels/RepositoryNode.cs @@ -13,7 +13,7 @@ public string Id get => _id; set { - var normalized = value.Replace('\\', '/'); + var normalized = value.Replace('\\', '/').TrimEnd('/'); SetProperty(ref _id, normalized); } } @@ -70,14 +70,14 @@ public List SubNodes public void Edit() { - var activePage = App.GetLauncer().ActivePage; + var activePage = App.GetLauncher().ActivePage; if (activePage != null && activePage.CanCreatePopup()) activePage.Popup = new EditRepositoryNode(this); } public void AddSubFolder() { - var activePage = App.GetLauncer().ActivePage; + var activePage = App.GetLauncher().ActivePage; if (activePage != null && activePage.CanCreatePopup()) activePage.Popup = new CreateGroup(this); } @@ -98,7 +98,7 @@ public void OpenTerminal() public void Delete() { - var activePage = App.GetLauncer().ActivePage; + var activePage = App.GetLauncher().ActivePage; if (activePage != null && activePage.CanCreatePopup()) activePage.Popup = new DeleteRepositoryNode(this); } diff --git a/src/ViewModels/RevisionCompare.cs b/src/ViewModels/RevisionCompare.cs index 1dad75935..39400aa3f 100644 --- a/src/ViewModels/RevisionCompare.cs +++ b/src/ViewModels/RevisionCompare.cs @@ -100,7 +100,7 @@ public void Dispose() public void NavigateTo(string commitSHA) { - var launcher = App.GetLauncer(); + var launcher = App.GetLauncher(); if (launcher == null) return; diff --git a/src/ViewModels/ScanRepositories.cs b/src/ViewModels/ScanRepositories.cs index cec932ebe..2618519b4 100644 --- a/src/ViewModels/ScanRepositories.cs +++ b/src/ViewModels/ScanRepositories.cs @@ -31,8 +31,8 @@ public override Task Sure() watch.Start(); var rootDir = new DirectoryInfo(RootDir); - var founded = new List(); - GetUnmanagedRepositories(rootDir, founded, new EnumerationOptions() + var found = new List(); + GetUnmanagedRepositories(rootDir, found, new EnumerationOptions() { AttributesToSkip = FileAttributes.Hidden | FileAttributes.System, IgnoreInaccessible = true, @@ -46,11 +46,11 @@ public override Task Sure() Dispatcher.UIThread.Invoke(() => { - var normalizedRoot = rootDir.FullName.Replace("\\", "/"); + var normalizedRoot = rootDir.FullName.Replace('\\', '/').TrimEnd('/'); - foreach (var f in founded) + foreach (var f in found) { - var parent = new DirectoryInfo(f.Path).Parent!.FullName.Replace("\\", "/"); + var parent = new DirectoryInfo(f.Path).Parent!.FullName.Replace('\\', '/').TrimEnd('/'); if (parent.Equals(normalizedRoot, StringComparison.Ordinal)) { Preferences.Instance.FindOrAddNodeByRepositoryPath(f.Path, null, false); @@ -64,6 +64,11 @@ public override Task Sure() } Preferences.Instance.AutoRemoveInvalidNode(); + + // Sort & Save unconditionally after a complete rescan. + Preferences.Instance.SortAllNodes(); + Preferences.Instance.Save(); + Welcome.Instance.Refresh(); }); @@ -93,7 +98,7 @@ private void GetUnmanagedRepositories(DirectoryInfo dir, List o CallUIThread(() => ProgressDescription = $"Scanning {subdir.FullName}..."); - var normalizedSelf = subdir.FullName.Replace("\\", "/"); + var normalizedSelf = subdir.FullName.Replace('\\', '/').TrimEnd('/'); if (_managed.Contains(normalizedSelf)) continue; @@ -103,7 +108,7 @@ private void GetUnmanagedRepositories(DirectoryInfo dir, List o var test = new Commands.QueryRepositoryRootPath(subdir.FullName).ReadToEnd(); if (test.IsSuccess && !string.IsNullOrEmpty(test.StdOut)) { - var normalized = test.StdOut.Trim().Replace("\\", "/"); + var normalized = test.StdOut.Trim().Replace('\\', '/').TrimEnd('/'); if (!_managed.Contains(normalized)) outs.Add(new FoundRepository(normalized, false)); } @@ -151,13 +156,7 @@ private RepositoryNode FindOrCreateGroup(List collection, string IsExpanded = true, }; collection.Add(added); - collection.Sort((l, r) => - { - if (l.IsRepository != r.IsRepository) - return l.IsRepository ? 1 : -1; - - return string.Compare(l.Name, r.Name, StringComparison.Ordinal); - }); + Preferences.Instance.SortNodes(collection); return added; } diff --git a/src/ViewModels/Welcome.cs b/src/ViewModels/Welcome.cs index 95f7f0100..069dcf38a 100644 --- a/src/ViewModels/Welcome.cs +++ b/src/ViewModels/Welcome.cs @@ -110,7 +110,7 @@ public void OpenOrInitRepository(string path, RepositoryNode parent, bool bMoveE var node = Preferences.Instance.FindOrAddNodeByRepositoryPath(repoRoot, parent, bMoveExistedNode); Refresh(); - var launcher = App.GetLauncer(); + var launcher = App.GetLauncher(); launcher?.OpenRepositoryInTab(node, launcher.ActivePage); } @@ -122,7 +122,7 @@ public void InitRepository(string path, RepositoryNode parent, string reason) return; } - var activePage = App.GetLauncer().ActivePage; + var activePage = App.GetLauncher().ActivePage; if (activePage != null && activePage.CanCreatePopup()) activePage.Popup = new Init(activePage.Node.Id, path, parent, reason); } @@ -135,7 +135,7 @@ public void Clone() return; } - var activePage = App.GetLauncer().ActivePage; + var activePage = App.GetLauncher().ActivePage; if (activePage != null && activePage.CanCreatePopup()) activePage.Popup = new Clone(activePage.Node.Id); } @@ -163,7 +163,7 @@ public void ScanDefaultCloneDir() return; } - var activePage = App.GetLauncer().ActivePage; + var activePage = App.GetLauncher().ActivePage; if (activePage != null && activePage.CanCreatePopup()) activePage.StartPopup(new ScanRepositories(defaultCloneDir)); } @@ -175,7 +175,7 @@ public void ClearSearchFilter() public void AddRootNode() { - var activePage = App.GetLauncer().ActivePage; + var activePage = App.GetLauncher().ActivePage; if (activePage != null && activePage.CanCreatePopup()) activePage.Popup = new CreateGroup(null); } @@ -197,7 +197,7 @@ public ContextMenu CreateContextMenu(RepositoryNode node) openAll.Icon = App.CreateMenuIcon("Icons.Folder.Open"); openAll.Click += (_, e) => { - OpenAllInNode(App.GetLauncer(), node); + OpenAllInNode(App.GetLauncher(), node); e.Handled = true; }; @@ -212,7 +212,7 @@ public ContextMenu CreateContextMenu(RepositoryNode node) open.Icon = App.CreateMenuIcon("Icons.Folder.Open"); open.Click += (_, e) => { - App.GetLauncer()?.OpenRepositoryInTab(node, null); + App.GetLauncher()?.OpenRepositoryInTab(node, null); e.Handled = true; }; @@ -267,7 +267,7 @@ public ContextMenu CreateContextMenu(RepositoryNode node) move.Icon = App.CreateMenuIcon("Icons.MoveToAnotherGroup"); move.Click += (_, e) => { - var activePage = App.GetLauncer().ActivePage; + var activePage = App.GetLauncher().ActivePage; if (activePage != null && activePage.CanCreatePopup()) activePage.Popup = new MoveRepositoryNode(node); diff --git a/src/ViewModels/WorkingCopy.cs b/src/ViewModels/WorkingCopy.cs index 6469b5641..ac6d9486d 100644 --- a/src/ViewModels/WorkingCopy.cs +++ b/src/ViewModels/WorkingCopy.cs @@ -780,7 +780,7 @@ public ContextMenu CreateContextMenuForUnstagedChanges() byParentFolder.IsVisible = !isRooted; byParentFolder.Click += (_, e) => { - var dir = Path.GetDirectoryName(change.Path)!.Replace("\\", "/"); + var dir = Path.GetDirectoryName(change.Path)!.Replace('\\', '/').TrimEnd('/'); Commands.GitIgnore.Add(_repo.FullPath, dir + "/"); e.Handled = true; }; @@ -802,7 +802,7 @@ public ContextMenu CreateContextMenuForUnstagedChanges() byExtensionInSameFolder.IsVisible = !isRooted; byExtensionInSameFolder.Click += (_, e) => { - var dir = Path.GetDirectoryName(change.Path)!.Replace("\\", "/"); + var dir = Path.GetDirectoryName(change.Path)!.Replace('\\', '/').TrimEnd('/'); Commands.GitIgnore.Add(_repo.FullPath, $"{dir}/*{extension}"); e.Handled = true; }; diff --git a/src/Views/LauncherTabBar.axaml b/src/Views/LauncherTabBar.axaml index a56da2b0f..f078a5988 100644 --- a/src/Views/LauncherTabBar.axaml +++ b/src/Views/LauncherTabBar.axaml @@ -211,7 +211,7 @@ - +