diff --git a/TRANSLATION.md b/TRANSLATION.md index 813b763c..13ac441a 100644 --- a/TRANSLATION.md +++ b/TRANSLATION.md @@ -6,32 +6,46 @@ This document shows the translation status of each locale file in the repository ### ![en_US](https://img.shields.io/badge/en__US-%E2%88%9A-brightgreen) -### ![de__DE](https://img.shields.io/badge/de__DE-97.53%25-yellow) +### ![de__DE](https://img.shields.io/badge/de__DE-96.15%25-yellow)
Missing keys in de_DE.axaml - Text.AIAssistant.Use +- Text.App.HideOthers - Text.Apply.3Way - Text.CheckoutBranchFromStash - Text.CheckoutBranchFromStash.Branch - Text.CheckoutBranchFromStash.Stash +- Text.Clone.Bookmark +- Text.Clone.Group - Text.CommandPalette.Branches - Text.CommandPalette.BranchesAndTags - Text.CommandPalette.RepositoryActions - Text.CommandPalette.RevisionFiles - Text.CommitMessageTextBox.Column +- Text.ConfigureCustomActionControls.StringFormatter +- Text.ConfigureCustomActionControls.StringFormatter.Tip +- Text.ConfigureCustomActionControls.UseFriendlyName - Text.ConfirmEmptyCommit.StageSelectedThenCommit +- Text.Diff.Submodule.UncommittedChanges - Text.Discard.IncludeModified - Text.GotoRevisionSelector +- Text.Hotkeys.Global.OpenLocalRepository - Text.Hotkeys.Repo.CreateBranch - Text.Hotkeys.Repo.GoToChild - Text.Init.CommandTip - Text.Init.ErrorMessageTip +- Text.OpenLocalRepository +- Text.OpenLocalRepository.Bookmark +- Text.OpenLocalRepository.Group +- Text.OpenLocalRepository.Path - Text.Preferences.AI.AdditionalPrompt - Text.Preferences.General.Use24Hours - Text.StashCM.ApplyFileChanges - Text.StashCM.Branch +- Text.SubmoduleRevisionCompare +- Text.SubmoduleRevisionCompare.OpenDetails - Text.Worktree.Branch - Text.Worktree.Head - Text.Worktree.Path @@ -40,13 +54,14 @@ This document shows the translation status of each locale file in the repository ### ![es__ES](https://img.shields.io/badge/es__ES-%E2%88%9A-brightgreen) -### ![fr__FR](https://img.shields.io/badge/fr__FR-91.37%25-yellow) +### ![fr__FR](https://img.shields.io/badge/fr__FR-90.06%25-yellow)
Missing keys in fr_FR.axaml - Text.About.ReleaseDate - Text.AIAssistant.Use +- Text.App.HideOthers - Text.Apply.3Way - Text.Blame.IgnoreWhitespace - Text.BranchCM.CompareTwo @@ -60,6 +75,8 @@ This document shows the translation status of each locale file in the repository - Text.CheckoutBranchFromStash - Text.CheckoutBranchFromStash.Branch - Text.CheckoutBranchFromStash.Stash +- Text.Clone.Bookmark +- Text.Clone.Group - Text.CommandPalette.Branches - Text.CommandPalette.BranchesAndTags - Text.CommandPalette.RepositoryActions @@ -68,7 +85,11 @@ This document shows the translation status of each locale file in the repository - Text.CommitMessageTextBox.Placeholder - Text.Compare.WithHead - Text.Configure.Git.AskBeforeAutoUpdatingSubmodules +- Text.ConfigureCustomActionControls.StringFormatter +- Text.ConfigureCustomActionControls.StringFormatter.Tip +- Text.ConfigureCustomActionControls.UseFriendlyName - Text.ConfirmEmptyCommit.StageSelectedThenCommit +- Text.Diff.Submodule.UncommittedChanges - Text.Discard.IncludeModified - Text.EditBranchDescription - Text.EditBranchDescription.Target @@ -76,6 +97,7 @@ This document shows the translation status of each locale file in the repository - Text.GotoRevisionSelector - Text.Histories.Header.DateTime - Text.Histories.ShowColumns +- Text.Hotkeys.Global.OpenLocalRepository - Text.Hotkeys.Global.ShowWorkspaceDropdownMenu - Text.Hotkeys.Global.Zoom - Text.Hotkeys.Repo.CreateBranch @@ -101,6 +123,10 @@ This document shows the translation status of each locale file in the repository - Text.MergeConflictEditor.Undo - Text.No - Text.OpenFile +- Text.OpenLocalRepository +- Text.OpenLocalRepository.Bookmark +- Text.OpenLocalRepository.Group +- Text.OpenLocalRepository.Path - Text.PageTabBar.Tab.MoveToWorkspace - Text.PageTabBar.Tab.Refresh - Text.Preferences.AI.AdditionalPrompt @@ -120,6 +146,8 @@ This document shows the translation status of each locale file in the repository - Text.SquashOrFixup.Into - Text.StashCM.ApplyFileChanges - Text.StashCM.Branch +- Text.SubmoduleRevisionCompare +- Text.SubmoduleRevisionCompare.OpenDetails - Text.TagCM.CompareTwo - Text.TagCM.CompareWith - Text.TagCM.CompareWithHead @@ -132,7 +160,7 @@ This document shows the translation status of each locale file in the repository
-### ![id__ID](https://img.shields.io/badge/id__ID-89.21%25-yellow) +### ![id__ID](https://img.shields.io/badge/id__ID-87.93%25-yellow)
Missing keys in id_ID.axaml @@ -140,6 +168,7 @@ This document shows the translation status of each locale file in the repository - Text.About.ReleaseDate - Text.About.ReleaseNotes - Text.AIAssistant.Use +- Text.App.HideOthers - Text.Apply.3Way - Text.Blame.BlameOnPreviousRevision - Text.Blame.IgnoreWhitespace @@ -156,6 +185,8 @@ This document shows the translation status of each locale file in the repository - Text.CheckoutBranchFromStash - Text.CheckoutBranchFromStash.Branch - Text.CheckoutBranchFromStash.Stash +- Text.Clone.Bookmark +- Text.Clone.Group - Text.CommandPalette.Branches - Text.CommandPalette.BranchesAndTags - Text.CommandPalette.RepositoryActions @@ -167,9 +198,13 @@ This document shows the translation status of each locale file in the repository - Text.Configure.CommitMessageTemplate.BuiltinVars - Text.Configure.Git.AskBeforeAutoUpdatingSubmodules - Text.Configure.Git.ConventionalTypesOverride +- Text.ConfigureCustomActionControls.StringFormatter +- Text.ConfigureCustomActionControls.StringFormatter.Tip - Text.ConfigureCustomActionControls.StringValue.Tip +- Text.ConfigureCustomActionControls.UseFriendlyName - Text.ConfirmEmptyCommit.StageSelectedThenCommit - Text.DealWithLocalChanges.DoNothing +- Text.Diff.Submodule.UncommittedChanges - Text.Discard.IncludeModified - Text.DropHead - Text.DropHead.Commit @@ -182,6 +217,7 @@ This document shows the translation status of each locale file in the repository - Text.GotoRevisionSelector - Text.Histories.Header.DateTime - Text.Histories.ShowColumns +- Text.Hotkeys.Global.OpenLocalRepository - Text.Hotkeys.Global.ShowWorkspaceDropdownMenu - Text.Hotkeys.Global.Zoom - Text.Hotkeys.Repo.CreateBranch @@ -212,6 +248,10 @@ This document shows the translation status of each locale file in the repository - Text.Open - Text.Open.SystemDefaultEditor - Text.OpenFile +- Text.OpenLocalRepository +- Text.OpenLocalRepository.Bookmark +- Text.OpenLocalRepository.Group +- Text.OpenLocalRepository.Path - Text.PageTabBar.Tab.MoveToWorkspace - Text.PageTabBar.Tab.Refresh - Text.Preferences.AI.AdditionalPrompt @@ -233,6 +273,8 @@ This document shows the translation status of each locale file in the repository - Text.SquashOrFixup.Into - Text.StashCM.ApplyFileChanges - Text.StashCM.Branch +- Text.SubmoduleRevisionCompare +- Text.SubmoduleRevisionCompare.OpenDetails - Text.TagCM.CompareTwo - Text.TagCM.CompareWith - Text.TagCM.CompareWithHead @@ -245,80 +287,109 @@ This document shows the translation status of each locale file in the repository
-### ![it__IT](https://img.shields.io/badge/it__IT-96.92%25-yellow) +### ![it__IT](https://img.shields.io/badge/it__IT-95.54%25-yellow)
Missing keys in it_IT.axaml - Text.AIAssistant.Use +- Text.App.HideOthers - Text.Apply.3Way - Text.ChangeCM.ResetFileTo - Text.CheckoutBranchFromStash - Text.CheckoutBranchFromStash.Branch - Text.CheckoutBranchFromStash.Stash +- Text.Clone.Bookmark +- Text.Clone.Group - Text.CommandPalette.Branches - Text.CommandPalette.BranchesAndTags - Text.CommandPalette.RepositoryActions - Text.CommandPalette.RevisionFiles - Text.CommitMessageTextBox.Column +- Text.ConfigureCustomActionControls.StringFormatter +- Text.ConfigureCustomActionControls.StringFormatter.Tip +- Text.ConfigureCustomActionControls.UseFriendlyName - Text.ConfirmEmptyCommit.StageSelectedThenCommit +- Text.Diff.Submodule.UncommittedChanges - Text.Discard.IncludeModified - Text.GotoRevisionSelector - Text.Histories.Header.DateTime - Text.Histories.ShowColumns +- Text.Hotkeys.Global.OpenLocalRepository - Text.Hotkeys.Repo.CreateBranch - Text.Hotkeys.Repo.GoToChild - Text.Hotkeys.Repo.GoToParent - Text.Init.CommandTip - Text.Init.ErrorMessageTip +- Text.OpenLocalRepository +- Text.OpenLocalRepository.Bookmark +- Text.OpenLocalRepository.Group +- Text.OpenLocalRepository.Path - Text.Preferences.AI.AdditionalPrompt - Text.Preferences.General.Use24Hours - Text.SelfUpdate.CurrentVersion - Text.SelfUpdate.ReleaseDate - Text.StashCM.ApplyFileChanges - Text.StashCM.Branch +- Text.SubmoduleRevisionCompare +- Text.SubmoduleRevisionCompare.OpenDetails - Text.Worktree.Branch - Text.Worktree.Head - Text.Worktree.Path
-### ![ja__JP](https://img.shields.io/badge/ja__JP-97.84%25-yellow) +### ![ja__JP](https://img.shields.io/badge/ja__JP-96.45%25-yellow)
Missing keys in ja_JP.axaml - Text.AIAssistant.Use +- Text.App.HideOthers - Text.Apply.3Way - Text.CheckoutBranchFromStash - Text.CheckoutBranchFromStash.Branch - Text.CheckoutBranchFromStash.Stash +- Text.Clone.Bookmark +- Text.Clone.Group - Text.CommandPalette.Branches - Text.CommandPalette.BranchesAndTags - Text.CommandPalette.RepositoryActions - Text.CommandPalette.RevisionFiles +- Text.ConfigureCustomActionControls.StringFormatter +- Text.ConfigureCustomActionControls.StringFormatter.Tip +- Text.ConfigureCustomActionControls.UseFriendlyName - Text.ConfirmEmptyCommit.StageSelectedThenCommit - Text.DealWithLocalChanges.DoNothing +- Text.Diff.Submodule.UncommittedChanges - Text.Discard.IncludeModified +- Text.Hotkeys.Global.OpenLocalRepository - Text.Hotkeys.Repo.CreateBranch - Text.Init.CommandTip - Text.Init.ErrorMessageTip +- Text.OpenLocalRepository +- Text.OpenLocalRepository.Bookmark +- Text.OpenLocalRepository.Group +- Text.OpenLocalRepository.Path - Text.Preferences.AI.AdditionalPrompt - Text.Preferences.General.Use24Hours - Text.StashCM.Branch +- Text.SubmoduleRevisionCompare +- Text.SubmoduleRevisionCompare.OpenDetails - Text.Worktree.Branch - Text.Worktree.Head - Text.Worktree.Path
-### ![ko__KR](https://img.shields.io/badge/ko__KR-89.52%25-yellow) +### ![ko__KR](https://img.shields.io/badge/ko__KR-88.24%25-yellow)
Missing keys in ko_KR.axaml - Text.About.ReleaseDate - Text.AIAssistant.Use +- Text.App.HideOthers - Text.Apply.3Way - Text.Blame.BlameOnPreviousRevision - Text.Blame.IgnoreWhitespace @@ -336,6 +407,8 @@ This document shows the translation status of each locale file in the repository - Text.CheckoutBranchFromStash - Text.CheckoutBranchFromStash.Branch - Text.CheckoutBranchFromStash.Stash +- Text.Clone.Bookmark +- Text.Clone.Group - Text.CommandPalette.Branches - Text.CommandPalette.BranchesAndTags - Text.CommandPalette.RepositoryActions @@ -345,9 +418,13 @@ This document shows the translation status of each locale file in the repository - Text.Compare.WithHead - Text.Configure.Git.AskBeforeAutoUpdatingSubmodules - Text.Configure.Git.ConventionalTypesOverride +- Text.ConfigureCustomActionControls.StringFormatter +- Text.ConfigureCustomActionControls.StringFormatter.Tip - Text.ConfigureCustomActionControls.StringValue.Tip +- Text.ConfigureCustomActionControls.UseFriendlyName - Text.ConfirmEmptyCommit.StageSelectedThenCommit - Text.DealWithLocalChanges.DoNothing +- Text.Diff.Submodule.UncommittedChanges - Text.Discard.IncludeModified - Text.EditBranchDescription - Text.EditBranchDescription.Target @@ -357,6 +434,7 @@ This document shows the translation status of each locale file in the repository - Text.GotoRevisionSelector - Text.Histories.Header.DateTime - Text.Histories.ShowColumns +- Text.Hotkeys.Global.OpenLocalRepository - Text.Hotkeys.Global.ShowWorkspaceDropdownMenu - Text.Hotkeys.Global.Zoom - Text.Hotkeys.Repo.CreateBranch @@ -387,6 +465,10 @@ This document shows the translation status of each locale file in the repository - Text.Open - Text.Open.SystemDefaultEditor - Text.OpenFile +- Text.OpenLocalRepository +- Text.OpenLocalRepository.Bookmark +- Text.OpenLocalRepository.Group +- Text.OpenLocalRepository.Path - Text.PageTabBar.Tab.MoveToWorkspace - Text.PageTabBar.Tab.Refresh - Text.Preferences.AI.AdditionalPrompt @@ -410,6 +492,8 @@ This document shows the translation status of each locale file in the repository - Text.StashCM.ApplyFileChanges - Text.StashCM.Branch - Text.Submodule.Status.Unmerged +- Text.SubmoduleRevisionCompare +- Text.SubmoduleRevisionCompare.OpenDetails - Text.TagCM.CompareTwo - Text.TagCM.CompareWith - Text.TagCM.CompareWithHead @@ -422,12 +506,13 @@ This document shows the translation status of each locale file in the repository
-### ![pt__BR](https://img.shields.io/badge/pt__BR-67.73%25-red) +### ![pt__BR](https://img.shields.io/badge/pt__BR-66.84%25-red)
Missing keys in pt_BR.axaml - Text.AIAssistant.Use +- Text.App.HideOthers - Text.Apply.3Way - Text.Blame.BlameOnPreviousRevision - Text.BranchCM.InteractiveRebase.Manually @@ -449,6 +534,8 @@ This document shows the translation status of each locale file in the repository - Text.CheckoutBranchFromStash - Text.CheckoutBranchFromStash.Branch - Text.CheckoutBranchFromStash.Stash +- Text.Clone.Bookmark +- Text.Clone.Group - Text.Clone.RecurseSubmodules - Text.CommandPalette.Branches - Text.CommandPalette.BranchesAndTags @@ -509,8 +596,11 @@ This document shows the translation status of each locale file in the repository - Text.ConfigureCustomActionControls.Label - Text.ConfigureCustomActionControls.Options - Text.ConfigureCustomActionControls.Options.Tip +- Text.ConfigureCustomActionControls.StringFormatter +- Text.ConfigureCustomActionControls.StringFormatter.Tip - Text.ConfigureCustomActionControls.StringValue.Tip - Text.ConfigureCustomActionControls.Type +- Text.ConfigureCustomActionControls.UseFriendlyName - Text.ConfirmEmptyCommit.Continue - Text.ConfirmEmptyCommit.NoLocalChanges - Text.ConfirmEmptyCommit.StageAllThenCommit @@ -538,6 +628,7 @@ This document shows the translation status of each locale file in the repository - Text.Diff.New - Text.Diff.Old - Text.Diff.Submodule.Deleted +- Text.Diff.Submodule.UncommittedChanges - Text.DirHistories - Text.DirtyState.HasLocalChanges - Text.DirtyState.HasPendingPullOrPush @@ -554,7 +645,6 @@ This document shows the translation status of each locale file in the repository - Text.Fetch.Force - Text.FileCM.CustomAction - Text.FileCM.ResolveUsing -- Text.GitFlow.FinishWithPush - Text.GitFlow.FinishWithSquash - Text.GitLFS.Locks.UnlockAllMyLocks - Text.GitLFS.Locks.UnlockAllMyLocks.Confirm @@ -562,6 +652,7 @@ This document shows the translation status of each locale file in the repository - Text.Histories.Header.DateTime - Text.Histories.ShowColumns - Text.Hotkeys.Global.Clone +- Text.Hotkeys.Global.OpenLocalRepository - Text.Hotkeys.Global.ShowWorkspaceDropdownMenu - Text.Hotkeys.Global.SwitchTab - Text.Hotkeys.Global.Zoom @@ -610,6 +701,10 @@ This document shows the translation status of each locale file in the repository - Text.Open - Text.Open.SystemDefaultEditor - Text.OpenFile +- Text.OpenLocalRepository +- Text.OpenLocalRepository.Bookmark +- Text.OpenLocalRepository.Group +- Text.OpenLocalRepository.Path - Text.PageTabBar.Tab.MoveToWorkspace - Text.PageTabBar.Tab.Refresh - Text.Preferences.AI.AdditionalPrompt @@ -706,6 +801,8 @@ This document shows the translation status of each locale file in the repository - Text.Submodule.Status.Unmerged - Text.Submodule.Update - Text.Submodule.URL +- Text.SubmoduleRevisionCompare +- Text.SubmoduleRevisionCompare.OpenDetails - Text.Tag.Tagger - Text.Tag.Time - Text.TagCM.CompareTwo @@ -744,9 +841,29 @@ This document shows the translation status of each locale file in the repository
-### ![ru__RU](https://img.shields.io/badge/ru__RU-%E2%88%9A-brightgreen) +### ![ru__RU](https://img.shields.io/badge/ru__RU-98.58%25-yellow) -### ![ta__IN](https://img.shields.io/badge/ta__IN-69.78%25-red) +
+Missing keys in ru_RU.axaml + +- Text.App.HideOthers +- Text.Clone.Bookmark +- Text.Clone.Group +- Text.ConfigureCustomActionControls.StringFormatter +- Text.ConfigureCustomActionControls.StringFormatter.Tip +- Text.ConfigureCustomActionControls.UseFriendlyName +- Text.Diff.Submodule.UncommittedChanges +- Text.Hotkeys.Global.OpenLocalRepository +- Text.OpenLocalRepository +- Text.OpenLocalRepository.Bookmark +- Text.OpenLocalRepository.Group +- Text.OpenLocalRepository.Path +- Text.SubmoduleRevisionCompare +- Text.SubmoduleRevisionCompare.OpenDetails + +
+ +### ![ta__IN](https://img.shields.io/badge/ta__IN-68.86%25-red)
Missing keys in ta_IN.axaml @@ -758,6 +875,7 @@ This document shows the translation status of each locale file in the repository - Text.AddToIgnore.Storage - Text.AIAssistant.Use - Text.App.Hide +- Text.App.HideOthers - Text.App.ShowAll - Text.Apply.3Way - Text.Askpass.Passphrase @@ -802,6 +920,8 @@ This document shows the translation status of each locale file in the repository - Text.CheckoutBranchFromStash - Text.CheckoutBranchFromStash.Branch - Text.CheckoutBranchFromStash.Stash +- Text.Clone.Bookmark +- Text.Clone.Group - Text.CommandPalette.Branches - Text.CommandPalette.BranchesAndTags - Text.CommandPalette.RepositoryActions @@ -853,8 +973,11 @@ This document shows the translation status of each locale file in the repository - Text.ConfigureCustomActionControls.Label - Text.ConfigureCustomActionControls.Options - Text.ConfigureCustomActionControls.Options.Tip +- Text.ConfigureCustomActionControls.StringFormatter +- Text.ConfigureCustomActionControls.StringFormatter.Tip - Text.ConfigureCustomActionControls.StringValue.Tip - Text.ConfigureCustomActionControls.Type +- Text.ConfigureCustomActionControls.UseFriendlyName - Text.ConfirmEmptyCommit.Continue - Text.ConfirmEmptyCommit.NoLocalChanges - Text.ConfirmEmptyCommit.StageAllThenCommit @@ -877,6 +1000,7 @@ This document shows the translation status of each locale file in the repository - Text.Diff.New - Text.Diff.Old - Text.Diff.Submodule.Deleted +- Text.Diff.Submodule.UncommittedChanges - Text.DirHistories - Text.DirtyState.HasLocalChanges - Text.DirtyState.HasPendingPullOrPush @@ -891,13 +1015,13 @@ This document shows the translation status of each locale file in the repository - Text.ExecuteCustomAction.Target - Text.ExecuteCustomAction.Repository - Text.FileCM.CustomAction -- Text.GitFlow.FinishWithPush - Text.GitFlow.FinishWithSquash - Text.GitLFS.Locks.UnlockAllMyLocks - Text.GitLFS.Locks.UnlockAllMyLocks.Confirm - Text.GotoRevisionSelector - Text.Histories.Header.DateTime - Text.Histories.ShowColumns +- Text.Hotkeys.Global.OpenLocalRepository - Text.Hotkeys.Global.ShowWorkspaceDropdownMenu - Text.Hotkeys.Global.SwitchTab - Text.Hotkeys.Global.Zoom @@ -937,6 +1061,10 @@ This document shows the translation status of each locale file in the repository - Text.Open - Text.Open.SystemDefaultEditor - Text.OpenFile +- Text.OpenLocalRepository +- Text.OpenLocalRepository.Bookmark +- Text.OpenLocalRepository.Group +- Text.OpenLocalRepository.Path - Text.PageTabBar.Tab.MoveToWorkspace - Text.PageTabBar.Tab.Refresh - Text.Preferences.AI.AdditionalPrompt @@ -1012,6 +1140,8 @@ This document shows the translation status of each locale file in the repository - Text.Submodule.Status.Unmerged - Text.Submodule.Update - Text.Submodule.URL +- Text.SubmoduleRevisionCompare +- Text.SubmoduleRevisionCompare.OpenDetails - Text.Tag.Tagger - Text.Tag.Time - Text.TagCM.CompareTwo @@ -1048,7 +1178,7 @@ This document shows the translation status of each locale file in the repository
-### ![uk__UA](https://img.shields.io/badge/uk__UA-70.61%25-red) +### ![uk__UA](https://img.shields.io/badge/uk__UA-69.68%25-red)
Missing keys in uk_UA.axaml @@ -1060,6 +1190,7 @@ This document shows the translation status of each locale file in the repository - Text.AddToIgnore.Storage - Text.AIAssistant.Use - Text.App.Hide +- Text.App.HideOthers - Text.App.ShowAll - Text.Apply.3Way - Text.Askpass.Passphrase @@ -1104,6 +1235,8 @@ This document shows the translation status of each locale file in the repository - Text.CheckoutBranchFromStash - Text.CheckoutBranchFromStash.Branch - Text.CheckoutBranchFromStash.Stash +- Text.Clone.Bookmark +- Text.Clone.Group - Text.CommandPalette.Branches - Text.CommandPalette.BranchesAndTags - Text.CommandPalette.RepositoryActions @@ -1154,8 +1287,11 @@ This document shows the translation status of each locale file in the repository - Text.ConfigureCustomActionControls.Label - Text.ConfigureCustomActionControls.Options - Text.ConfigureCustomActionControls.Options.Tip +- Text.ConfigureCustomActionControls.StringFormatter +- Text.ConfigureCustomActionControls.StringFormatter.Tip - Text.ConfigureCustomActionControls.StringValue.Tip - Text.ConfigureCustomActionControls.Type +- Text.ConfigureCustomActionControls.UseFriendlyName - Text.ConfigureWorkspace.Name - Text.ConfirmEmptyCommit.StageSelectedThenCommit - Text.ConfirmRestart.Title @@ -1175,6 +1311,7 @@ This document shows the translation status of each locale file in the repository - Text.Diff.New - Text.Diff.Old - Text.Diff.Submodule.Deleted +- Text.Diff.Submodule.UncommittedChanges - Text.DirHistories - Text.DirtyState.HasLocalChanges - Text.DirtyState.HasPendingPullOrPush @@ -1189,13 +1326,13 @@ This document shows the translation status of each locale file in the repository - Text.ExecuteCustomAction.Target - Text.ExecuteCustomAction.Repository - Text.FileCM.CustomAction -- Text.GitFlow.FinishWithPush - Text.GitFlow.FinishWithSquash - Text.GitLFS.Locks.UnlockAllMyLocks - Text.GitLFS.Locks.UnlockAllMyLocks.Confirm - Text.GotoRevisionSelector - Text.Histories.Header.DateTime - Text.Histories.ShowColumns +- Text.Hotkeys.Global.OpenLocalRepository - Text.Hotkeys.Global.ShowWorkspaceDropdownMenu - Text.Hotkeys.Global.SwitchTab - Text.Hotkeys.Global.Zoom @@ -1235,6 +1372,10 @@ This document shows the translation status of each locale file in the repository - Text.Open - Text.Open.SystemDefaultEditor - Text.OpenFile +- Text.OpenLocalRepository +- Text.OpenLocalRepository.Bookmark +- Text.OpenLocalRepository.Group +- Text.OpenLocalRepository.Path - Text.PageTabBar.Tab.MoveToWorkspace - Text.PageTabBar.Tab.Refresh - Text.Preferences.AI.AdditionalPrompt @@ -1310,6 +1451,8 @@ This document shows the translation status of each locale file in the repository - Text.Submodule.Status.Unmerged - Text.Submodule.Update - Text.Submodule.URL +- Text.SubmoduleRevisionCompare +- Text.SubmoduleRevisionCompare.OpenDetails - Text.Tag.Tagger - Text.Tag.Time - Text.TagCM.CompareTwo diff --git a/VERSION b/VERSION index 1628770a..0180a8fc 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2026.08 \ No newline at end of file +2026.09 \ No newline at end of file diff --git a/src/AI/Service.cs b/src/AI/Service.cs index f7eb25e6..ebf9170e 100644 --- a/src/AI/Service.cs +++ b/src/AI/Service.cs @@ -2,7 +2,6 @@ using System.ClientModel; using System.Collections.Generic; using System.Text.Json.Serialization; -using System.Threading.Tasks; using Azure.AI.OpenAI; using CommunityToolkit.Mvvm.ComponentModel; using OpenAI; @@ -55,7 +54,7 @@ namespace SourceGit.AI set; } = string.Empty; - public async Task> FetchAvailableModelsAsync() + public void FetchAvailableModels() { var allModels = GetOpenAIClient().GetOpenAIModelClient().GetModels(); AvailableModels = new List(); @@ -71,8 +70,6 @@ namespace SourceGit.AI { Model = null; } - - return AvailableModels; } public ChatClient GetChatClient() @@ -84,8 +81,8 @@ namespace SourceGit.AI { var credential = new ApiKeyCredential(ReadApiKeyFromEnv ? Environment.GetEnvironmentVariable(ApiKey) : ApiKey); return Server.Contains("openai.azure.com/", StringComparison.Ordinal) - ? new AzureOpenAIClient(new Uri(Server), credential, new AzureOpenAIClientOptions() { UserAgentApplicationId = string.Empty }) - : new OpenAIClient(credential, new() { Endpoint = new Uri(Server), UserAgentApplicationId = string.Empty }); + ? new AzureOpenAIClient(new Uri(Server), credential) + : new OpenAIClient(credential, new() { Endpoint = new Uri(Server) }); } private string _name = string.Empty; diff --git a/src/App.Commands.cs b/src/App.Commands.cs index 54175070..daa930b7 100644 --- a/src/App.Commands.cs +++ b/src/App.Commands.cs @@ -37,23 +37,61 @@ namespace SourceGit } } - public static readonly Command OpenPreferencesCommand = new Command(async _ => await ShowDialog(new Views.Preferences())); - public static readonly Command OpenHotkeysCommand = new Command(async _ => await ShowDialog(new Views.Hotkeys())); - public static readonly Command OpenAppDataDirCommand = new Command(_ => Native.OS.OpenInFileManager(Native.OS.DataDir)); - public static readonly Command OpenAboutCommand = new Command(async _ => await ShowDialog(new Views.About())); - public static readonly Command CheckForUpdateCommand = new Command(_ => (Current as App)?.Check4Update(true)); - public static readonly Command QuitCommand = new Command(_ => Quit(0)); + public static readonly Command OpenPreferencesCommand = new Command(async _ => + { + if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner }) + { + var dialog = new Views.Preferences(); + await dialog.ShowDialog(owner); + } + }); + + public static readonly Command OpenHotkeysCommand = new Command(async _ => + { + if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner }) + { + var dialog = new Views.Hotkeys(); + await dialog.ShowDialog(owner); + } + }); + + public static readonly Command OpenAppDataDirCommand = new Command(_ => + { + Native.OS.OpenInFileManager(Native.OS.DataDir); + }); + + public static readonly Command OpenAboutCommand = new Command(async _ => + { + if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner }) + { + var dialog = new Views.About(); + await dialog.ShowDialog(owner); + } + }); + + public static readonly Command CheckForUpdateCommand = new Command(_ => + { + (Current as App)?.Check4Update(true); + }); + + public static readonly Command QuitCommand = new Command(_ => + { + Quit(0); + }); public static readonly Command HideAppCommand = new Command(_ => { - if (Current is App app && app.TryGetFeature(typeof(IActivatableLifetime)) is IActivatableLifetime lifetime) - lifetime.TryEnterBackground(); + Native.OS.HideSelf(); }); - public static readonly Command ShowAppCommand = new Command(_ => + public static readonly Command HideOtherApplicationsCommand = new Command(_ => { - if (Current is App app && app.TryGetFeature(typeof(IActivatableLifetime)) is IActivatableLifetime lifetime) - lifetime.TryLeaveBackground(); + Native.OS.HideOtherApplications(); + }); + + public static readonly Command ShowAllApplicationsCommand = new Command(_ => + { + Native.OS.ShowAllApplications(); }); } } diff --git a/src/App.Extensions.cs b/src/App.Extensions.cs index 1819d3c8..7c2d0195 100644 --- a/src/App.Extensions.cs +++ b/src/App.Extensions.cs @@ -1,5 +1,8 @@ using System; +using System.Collections.Generic; using System.IO; +using System.Text; +using Avalonia.Media; namespace SourceGit { @@ -14,6 +17,47 @@ namespace SourceGit { return value.Replace("\"", "\\\"", StringComparison.Ordinal); } + + public static string FormatFontNames(string input) + { + if (string.IsNullOrEmpty(input)) + return string.Empty; + + var parts = input.Split(','); + var trimmed = new List(); + + foreach (var part in parts) + { + var t = part.Trim(); + if (string.IsNullOrEmpty(t)) + continue; + + var sb = new StringBuilder(); + var prevChar = '\0'; + + foreach (var c in t) + { + if (c == ' ' && prevChar == ' ') + continue; + sb.Append(c); + prevChar = c; + } + + var name = sb.ToString(); + try + { + var fontFamily = FontFamily.Parse(name); + if (fontFamily.FamilyTypefaces.Count > 0) + trimmed.Add(name); + } + catch + { + // Ignore exceptions. + } + } + + return trimmed.Count > 0 ? string.Join(',', trimmed) : string.Empty; + } } public static class CommandExtensions diff --git a/src/App.axaml b/src/App.axaml index a7a0c17f..2fa98263 100644 --- a/src/App.axaml +++ b/src/App.axaml @@ -45,7 +45,8 @@ - + + diff --git a/src/App.axaml.cs b/src/App.axaml.cs index 44dbc977..7a230dc1 100644 --- a/src/App.axaml.cs +++ b/src/App.axaml.cs @@ -1,8 +1,6 @@ using System; -using System.Collections.Generic; using System.IO; using System.Net.Http; -using System.Text; using System.Text.Json; using System.Threading.Tasks; @@ -75,79 +73,6 @@ namespace SourceGit #endregion #region Utility Functions - public static Task ShowDialog(object data, Window owner = null) - { - if (owner == null) - { - if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } mainWindow }) - owner = mainWindow; - else - return null; - } - - if (data is Views.ChromelessWindow window) - return window.ShowDialog(owner); - - window = Views.ControlExtensions.CreateFromViewModels(data) as Views.ChromelessWindow; - if (window != null) - { - window.DataContext = data; - return window.ShowDialog(owner); - } - - return null; - } - - public static void ShowWindow(object data) - { - if (data is not Views.ChromelessWindow window) - { - window = Views.ControlExtensions.CreateFromViewModels(data) as Views.ChromelessWindow; - if (window == null) - return; - - window.DataContext = data; - } - - do - { - if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { Windows: { Count: > 0 } windows }) - { - // Try to find the actived window (fall back to `MainWindow`) - Window actived = windows[0]; - if (!actived.IsActive) - { - for (var i = 1; i < windows.Count; i++) - { - var test = windows[i]; - if (test.IsActive) - { - actived = test; - break; - } - } - } - - // Get the screen where current window locates. - var screen = actived.Screens.ScreenFromWindow(actived) ?? actived.Screens.Primary; - if (screen == null) - break; - - // Calculate the startup position (Center Screen Mode) of target window - var rect = new PixelRect(PixelSize.FromSize(window.ClientSize, actived.DesktopScaling)); - var centeredRect = screen.WorkingArea.CenterRect(rect); - if (actived.Screens.ScreenFromPoint(centeredRect.Position) == null) - break; - - // Use the startup position - window.WindowStartupLocation = WindowStartupLocation.Manual; - window.Position = centeredRect.Position; - } - } while (false); - - window.Show(); - } - public static async Task AskConfirmAsync(string message, Models.ConfirmButtonType buttonType = Models.ConfirmButtonType.OkCancel) { if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner }) @@ -253,8 +178,8 @@ namespace SourceGit app._fontsOverrides = null; } - defaultFont = app.FixFontFamilyName(defaultFont); - monospaceFont = app.FixFontFamilyName(monospaceFont); + defaultFont = StringExtensions.FormatFontNames(defaultFont); + monospaceFont = StringExtensions.FormatFontNames(monospaceFont); var resDic = new ResourceDictionary(); if (!string.IsNullOrEmpty(defaultFont)) @@ -303,15 +228,9 @@ namespace SourceGit public static void Quit(int exitCode) { if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) - { - desktop.ShutdownMode = ShutdownMode.OnExplicitShutdown; - desktop.MainWindow?.Close(); desktop.Shutdown(exitCode); - } else - { Environment.Exit(exitCode); - } } #endregion @@ -547,10 +466,27 @@ namespace SourceGit var pref = ViewModels.Preferences.Instance; pref.SetCanModify(); + pref.UpdateAvailableAIModels(); _launcher = new ViewModels.Launcher(startupRepo); desktop.MainWindow = new Views.Launcher() { DataContext = _launcher }; - desktop.ShutdownMode = ShutdownMode.OnMainWindowClose; + desktop.ShutdownMode = ShutdownMode.OnExplicitShutdown; + + // Fix macOS crash when quiting from Dock + if (OperatingSystem.IsMacOS()) + { + desktop.ShutdownRequested += (_, e) => + { + e.Cancel = true; + Dispatcher.UIThread.Post(() => Quit(0)); + }; + } + + desktop.Exit += (_, _) => + { + _ipcChannel?.Dispose(); + _ipcChannel = null; + }; _ipcChannel.MessageReceived += repo => { @@ -562,8 +498,6 @@ namespace SourceGit }); }; - desktop.Exit += (_, _) => _ipcChannel.Dispose(); - #if !DISABLE_UPDATE_DETECTION if (pref.ShouldCheck4UpdateOnStartup()) Check4Update(); @@ -619,7 +553,12 @@ namespace SourceGit { Dispatcher.UIThread.Invoke(async () => { - await ShowDialog(new ViewModels.SelfUpdate { Data = data }); + if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner }) + { + var ctx = new ViewModels.SelfUpdate { Data = data }; + var dialog = new Views.SelfUpdate() { DataContext = ctx }; + await dialog.ShowDialog(owner); + } }); } catch @@ -629,47 +568,6 @@ namespace SourceGit } #endregion - private string FixFontFamilyName(string input) - { - if (string.IsNullOrEmpty(input)) - return string.Empty; - - var parts = input.Split(','); - var trimmed = new List(); - - foreach (var part in parts) - { - var t = part.Trim(); - if (string.IsNullOrEmpty(t)) - continue; - - var sb = new StringBuilder(); - var prevChar = '\0'; - - foreach (var c in t) - { - if (c == ' ' && prevChar == ' ') - continue; - sb.Append(c); - prevChar = c; - } - - var name = sb.ToString(); - try - { - var fontFamily = FontFamily.Parse(name); - if (fontFamily.FamilyTypefaces.Count > 0) - trimmed.Add(name); - } - catch - { - // Ignore exceptions. - } - } - - return trimmed.Count > 0 ? string.Join(',', trimmed) : string.Empty; - } - private Models.IpcChannel _ipcChannel = null; private ViewModels.Launcher _launcher = null; private ResourceDictionary _activeLocale = null; diff --git a/src/Commands/Diff.cs b/src/Commands/Diff.cs index 4d0cc72a..4840d14e 100644 --- a/src/Commands/Diff.cs +++ b/src/Commands/Diff.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -46,23 +47,28 @@ namespace SourceGit.Commands proc.StartInfo = CreateGitStartInfo(true); proc.Start(); - var text = await proc.StandardOutput.ReadToEndAsync().ConfigureAwait(false); + using var ms = new MemoryStream(); + await proc.StandardOutput.BaseStream.CopyToAsync(ms, CancellationToken).ConfigureAwait(false); + var bytes = ms.ToArray(); var start = 0; - var end = text.IndexOf('\n', start); - while (end > 0) + while (start < bytes.Length) { - var line = text[start..end]; - ParseLine(line); + var end = Array.IndexOf(bytes, (byte)'\n', start); + if (end < 0) + { + ParseLine(bytes[start..]); + break; + } + + ParseLine(bytes[start..end]); + if (_result.IsBinary) + break; start = end + 1; - end = text.IndexOf('\n', start); } - if (start < text.Length) - ParseLine(text[start..]); - - await proc.WaitForExitAsync().ConfigureAwait(false); + await proc.WaitForExitAsync(CancellationToken).ConfigureAwait(false); } catch { @@ -82,66 +88,14 @@ namespace SourceGit.Commands return _result; } - private void ParseLine(string line) + private void ParseLine(byte[] lineBytes) { - if (_result.IsBinary) + var line = Encoding.UTF8.GetString(lineBytes); + if (ParseFileModeChange(line)) return; - if (line.StartsWith("old mode ", StringComparison.Ordinal)) - { - _result.OldMode = line.Substring(9); + if (ParseLFSChange(line)) return; - } - - if (line.StartsWith("new mode ", StringComparison.Ordinal)) - { - _result.NewMode = line.Substring(9); - return; - } - - if (line.StartsWith("deleted file mode ", StringComparison.Ordinal)) - { - _result.OldMode = line.Substring(18); - return; - } - - if (line.StartsWith("new file mode ", StringComparison.Ordinal)) - { - _result.NewMode = line.Substring(14); - return; - } - - if (_result.IsLFS) - { - var ch = line[0]; - if (ch == '-') - { - if (line.StartsWith("-oid sha256:", StringComparison.Ordinal)) - { - _result.LFSDiff.Old.Oid = line.Substring(12); - } - else if (line.StartsWith("-size ", StringComparison.Ordinal)) - { - _result.LFSDiff.Old.Size = long.Parse(line.AsSpan(6)); - } - } - else if (ch == '+') - { - if (line.StartsWith("+oid sha256:", StringComparison.Ordinal)) - { - _result.LFSDiff.New.Oid = line.Substring(12); - } - else if (line.StartsWith("+size ", StringComparison.Ordinal)) - { - _result.LFSDiff.New.Size = long.Parse(line.AsSpan(6)); - } - } - else if (line.StartsWith(" size ", StringComparison.Ordinal)) - { - _result.LFSDiff.New.Size = _result.LFSDiff.Old.Size = long.Parse(line.AsSpan(6)); - } - return; - } if (_result.TextDiff.Lines.Count == 0) { @@ -168,7 +122,7 @@ namespace SourceGit.Commands _oldLine = int.Parse(match.Groups[1].Value); _newLine = int.Parse(match.Groups[2].Value); - _last = new Models.TextDiffLine(Models.TextDiffLineType.Indicator, line, 0, 0); + _last = new Models.TextDiffLine(Models.TextDiffLineType.Indicator, line, lineBytes, 0, 0); _result.TextDiff.Lines.Add(_last); } } @@ -177,7 +131,7 @@ namespace SourceGit.Commands if (line.Length == 0) { ProcessInlineHighlights(); - _last = new Models.TextDiffLine(Models.TextDiffLineType.Normal, "", _oldLine, _newLine); + _last = new Models.TextDiffLine(Models.TextDiffLineType.Normal, "", [], _oldLine, _newLine); _result.TextDiff.Lines.Add(_last); _oldLine++; _newLine++; @@ -187,29 +141,15 @@ namespace SourceGit.Commands var ch = line[0]; if (ch == '-') { - if (_oldLine == 1 && _newLine == 0 && line.StartsWith(PREFIX_LFS_DEL, StringComparison.Ordinal)) - { - _result.IsLFS = true; - _result.LFSDiff = new Models.LFSDiff(); - return; - } - _result.TextDiff.DeletedLines++; - _last = new Models.TextDiffLine(Models.TextDiffLineType.Deleted, line.Substring(1), _oldLine, 0); + _last = new Models.TextDiffLine(Models.TextDiffLineType.Deleted, line.Substring(1), lineBytes[1..], _oldLine, 0); _deleted.Add(_last); _oldLine++; } else if (ch == '+') { - if (_oldLine == 0 && _newLine == 1 && line.StartsWith(PREFIX_LFS_NEW, StringComparison.Ordinal)) - { - _result.IsLFS = true; - _result.LFSDiff = new Models.LFSDiff(); - return; - } - _result.TextDiff.AddedLines++; - _last = new Models.TextDiffLine(Models.TextDiffLineType.Added, line.Substring(1), 0, _newLine); + _last = new Models.TextDiffLine(Models.TextDiffLineType.Added, line.Substring(1), lineBytes[1..], 0, _newLine); _added.Add(_last); _newLine++; } @@ -221,19 +161,12 @@ namespace SourceGit.Commands { _oldLine = int.Parse(match.Groups[1].Value); _newLine = int.Parse(match.Groups[2].Value); - _last = new Models.TextDiffLine(Models.TextDiffLineType.Indicator, line, 0, 0); + _last = new Models.TextDiffLine(Models.TextDiffLineType.Indicator, line, lineBytes, 0, 0); _result.TextDiff.Lines.Add(_last); } else { - if (_oldLine == 1 && _newLine == 1 && line.StartsWith(PREFIX_LFS_MODIFY, StringComparison.Ordinal)) - { - _result.IsLFS = true; - _result.LFSDiff = new Models.LFSDiff(); - return; - } - - _last = new Models.TextDiffLine(Models.TextDiffLineType.Normal, line.Substring(1), _oldLine, _newLine); + _last = new Models.TextDiffLine(Models.TextDiffLineType.Normal, line.Substring(1), lineBytes[1..], _oldLine, _newLine); _result.TextDiff.Lines.Add(_last); _oldLine++; _newLine++; @@ -246,6 +179,70 @@ namespace SourceGit.Commands } } + private bool ParseFileModeChange(string line) + { + if (line.StartsWith("old mode ", StringComparison.Ordinal)) + { + _result.OldMode = line.Substring(9); + return true; + } + + if (line.StartsWith("new mode ", StringComparison.Ordinal)) + { + _result.NewMode = line.Substring(9); + return true; + } + + if (line.StartsWith("deleted file mode ", StringComparison.Ordinal)) + { + _result.OldMode = line.Substring(18); + return true; + } + + if (line.StartsWith("new file mode ", StringComparison.Ordinal)) + { + _result.NewMode = line.Substring(14); + return true; + } + + return false; + } + + private bool ParseLFSChange(string line) + { + if (_result.IsLFS) + { + if (line.StartsWith("-oid sha256:", StringComparison.Ordinal)) + _result.LFSDiff.Old.Oid = line.Substring(12); + else if (line.StartsWith("-size ", StringComparison.Ordinal)) + _result.LFSDiff.Old.Size = long.Parse(line.AsSpan(6)); + else if (line.StartsWith("+oid sha256:", StringComparison.Ordinal)) + _result.LFSDiff.New.Oid = line.Substring(12); + else if (line.StartsWith("+size ", StringComparison.Ordinal)) + _result.LFSDiff.New.Size = long.Parse(line.AsSpan(6)); + else if (line.StartsWith(" size ", StringComparison.Ordinal)) + _result.LFSDiff.New.Size = _result.LFSDiff.Old.Size = long.Parse(line.AsSpan(6)); + + return true; + } + + if (_result.TextDiff.Lines.Count != 1) + return false; + + var isLFS = (_oldLine == 1 && _newLine == 1 && line.StartsWith(PREFIX_LFS_MODIFY, StringComparison.Ordinal)) || + (_oldLine == 1 && _newLine == 0 && line.StartsWith(PREFIX_LFS_DEL, StringComparison.Ordinal)) || + (_oldLine == 0 && _newLine == 1 && line.StartsWith(PREFIX_LFS_NEW, StringComparison.Ordinal)); + + if (isLFS) + { + _result.IsLFS = true; + _result.LFSDiff = new Models.LFSDiff(); + return true; + } + + return false; + } + private void ProcessInlineHighlights() { if (_deleted.Count > 0) diff --git a/src/Commands/GitFlow.cs b/src/Commands/GitFlow.cs index c2052f0e..8e29e6b5 100644 --- a/src/Commands/GitFlow.cs +++ b/src/Commands/GitFlow.cs @@ -49,7 +49,7 @@ namespace SourceGit.Commands return await start.Use(log).ExecAsync().ConfigureAwait(false); } - public static async Task FinishAsync(string repo, Models.GitFlowBranchType type, string name, bool squash, bool push, bool keepBranch, Models.ICommandLog log) + public static async Task FinishAsync(string repo, Models.GitFlowBranchType type, string name, bool squash, bool keepBranch, Models.ICommandLog log) { var builder = new StringBuilder(); builder.Append("flow "); @@ -73,8 +73,6 @@ namespace SourceGit.Commands builder.Append(" finish "); if (squash) builder.Append("--squash "); - if (push) - builder.Append("--push "); if (keepBranch) builder.Append("-k "); builder.Append(name); diff --git a/src/Commands/QueryCommitsForInteractiveRebase.cs b/src/Commands/QueryCommitsForInteractiveRebase.cs index 8af420d3..7f0d1ba1 100644 --- a/src/Commands/QueryCommitsForInteractiveRebase.cs +++ b/src/Commands/QueryCommitsForInteractiveRebase.cs @@ -12,7 +12,7 @@ namespace SourceGit.Commands WorkingDirectory = repo; Context = repo; - Args = $"log --topo-order --cherry-pick --right-only --no-merges --no-show-signature --decorate=full --format=\"%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%B%n{_boundary}\" {on}...HEAD"; + Args = $"log --topo-order --cherry-pick --right-only --no-merges --no-show-signature --decorate=full --format=\"%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%s%n%B%n{_boundary}\" {on}...HEAD"; } public async Task> GetResultAsync() @@ -58,6 +58,9 @@ namespace SourceGit.Commands case 6: current.Commit.CommitterTime = ulong.Parse(line); break; + case 7: + current.Commit.Subject = line; + break; default: var boundary = rs.StdOut.IndexOf(_boundary, end + 1, StringComparison.Ordinal); if (boundary > end) diff --git a/src/Models/CustomAction.cs b/src/Models/CustomAction.cs index ec500d3c..59652d32 100644 --- a/src/Models/CustomAction.cs +++ b/src/Models/CustomAction.cs @@ -19,6 +19,8 @@ namespace SourceGit.Models PathSelector, CheckBox, ComboBox, + LocalBranchSelector, + RemoteBranchSelector, } public record CustomActionTargetFile(string File, Commit Revision); @@ -49,6 +51,12 @@ namespace SourceGit.Models set => SetProperty(ref _stringValue, value); } + public string StringFormatter + { + get => _stringFormatter; + set => SetProperty(ref _stringFormatter, value); + } + public bool BoolValue { get => _boolValue; @@ -59,6 +67,7 @@ namespace SourceGit.Models private string _label = string.Empty; private string _description = string.Empty; private string _stringValue = string.Empty; + private string _stringFormatter = string.Empty; private bool _boolValue = false; } diff --git a/src/Models/DiffResult.cs b/src/Models/DiffResult.cs index 32fff76c..27a8ff09 100644 --- a/src/Models/DiffResult.cs +++ b/src/Models/DiffResult.cs @@ -23,6 +23,7 @@ namespace SourceGit.Models public class TextDiffLine { public TextDiffLineType Type { get; set; } = TextDiffLineType.None; + public byte[] RawContent { get; set; } = []; public string Content { get; set; } = ""; public int OldLineNumber { get; set; } = 0; public int NewLineNumber { get; set; } = 0; @@ -33,10 +34,11 @@ namespace SourceGit.Models public string NewLine => NewLineNumber == 0 ? string.Empty : NewLineNumber.ToString(); public TextDiffLine() { } - public TextDiffLine(TextDiffLineType type, string content, int oldLine, int newLine) + public TextDiffLine(TextDiffLineType type, string line, byte[] rawContent, int oldLine, int newLine) { Type = type; - Content = content; + Content = line; + RawContent = rawContent; OldLineNumber = oldLine; NewLineNumber = newLine; } @@ -97,19 +99,19 @@ namespace SourceGit.Models return rs; } - public void GenerateNewPatchFromSelection(Change change, string fileBlobGuid, TextDiffSelection selection, bool revert, string output) + public void GenerateNewPatchFromSelection(string file, string fileBlobGuid, TextDiffSelection selection, bool revert, string output) { var isTracked = !string.IsNullOrEmpty(fileBlobGuid); var fileGuid = isTracked ? fileBlobGuid : "00000000"; using var writer = new StreamWriter(output); writer.NewLine = "\n"; - writer.WriteLine($"diff --git a/{change.Path} b/{change.Path}"); + writer.WriteLine($"diff --git a/{file} b/{file}"); if (!revert && !isTracked) writer.WriteLine("new file mode 100644"); writer.WriteLine($"index 00000000...{fileGuid}"); - writer.WriteLine($"--- {(revert || isTracked ? $"a/{change.Path}" : "/dev/null")}"); - writer.WriteLine($"+++ b/{change.Path}"); + writer.WriteLine($"--- {(revert || isTracked ? $"a/{file}" : "/dev/null")}"); + writer.WriteLine($"+++ b/{file}"); var additions = selection.EndLine - selection.StartLine; if (selection.StartLine != 1) @@ -146,19 +148,17 @@ namespace SourceGit.Models writer.Flush(); } - public void GeneratePatchFromSelection(Change change, string fileTreeGuid, TextDiffSelection selection, bool revert, string output) + public void GeneratePatchFromSelection(string file, string fileTreeGuid, TextDiffSelection selection, bool revert, string output) { - var orgFile = !string.IsNullOrEmpty(change.OriginalPath) ? change.OriginalPath : change.Path; - using var writer = new StreamWriter(output); writer.NewLine = "\n"; - writer.WriteLine($"diff --git a/{change.Path} b/{change.Path}"); + writer.WriteLine($"diff --git a/{file} b/{file}"); writer.WriteLine($"index 00000000...{fileTreeGuid} 100644"); - writer.WriteLine($"--- a/{orgFile}"); - writer.WriteLine($"+++ b/{change.Path}"); + writer.WriteLine($"--- a/{file}"); + writer.WriteLine($"+++ b/{file}"); // If last line of selection is a change. Find one more line. - string tail = null; + TextDiffLine tail = null; if (selection.EndLine < Lines.Count) { var lastLine = Lines[selection.EndLine - 1]; @@ -173,7 +173,7 @@ namespace SourceGit.Models (revert && line.Type == TextDiffLineType.Added) || (!revert && line.Type == TextDiffLineType.Deleted)) { - tail = line.Content; + tail = line; break; } } @@ -256,24 +256,22 @@ namespace SourceGit.Models } } - if (!string.IsNullOrEmpty(tail)) - writer.WriteLine($" {tail}"); + if (tail != null) + WriteLine(writer, ' ', tail); writer.Flush(); } - public void GeneratePatchFromSelectionSingleSide(Change change, string fileTreeGuid, TextDiffSelection selection, bool revert, bool isOldSide, string output) + public void GeneratePatchFromSelectionSingleSide(string file, string fileTreeGuid, TextDiffSelection selection, bool revert, bool isOldSide, string output) { - var orgFile = !string.IsNullOrEmpty(change.OriginalPath) ? change.OriginalPath : change.Path; - using var writer = new StreamWriter(output); writer.NewLine = "\n"; - writer.WriteLine($"diff --git a/{change.Path} b/{change.Path}"); + writer.WriteLine($"diff --git a/{file} b/{file}"); writer.WriteLine($"index 00000000...{fileTreeGuid} 100644"); - writer.WriteLine($"--- a/{orgFile}"); - writer.WriteLine($"+++ b/{change.Path}"); + writer.WriteLine($"--- a/{file}"); + writer.WriteLine($"+++ b/{file}"); // If last line of selection is a change. Find one more line. - string tail = null; + TextDiffLine tail = null; if (selection.EndLine < Lines.Count) { var lastLine = Lines[selection.EndLine - 1]; @@ -288,7 +286,7 @@ namespace SourceGit.Models { if (line.Type == TextDiffLineType.Normal || line.Type == TextDiffLineType.Added) { - tail = line.Content; + tail = line; break; } } @@ -296,7 +294,7 @@ namespace SourceGit.Models { if (line.Type == TextDiffLineType.Normal || line.Type == TextDiffLineType.Deleted) { - tail = line.Content; + tail = line; break; } } @@ -408,8 +406,8 @@ namespace SourceGit.Models } } - if (!string.IsNullOrEmpty(tail)) - writer.WriteLine($" {tail}"); + if (tail != null) + WriteLine(writer, ' ', tail); writer.Flush(); } @@ -564,7 +562,11 @@ namespace SourceGit.Models private static void WriteLine(StreamWriter writer, char prefix, TextDiffLine line) { - writer.WriteLine($"{prefix}{line.Content}"); + writer.Flush(); + + writer.BaseStream.WriteByte((byte)prefix); + writer.BaseStream.Write(line.RawContent); + writer.BaseStream.WriteByte((byte)'\n'); if (line.NoNewLineEndOfFile) writer.WriteLine("\\ No newline at end of file"); @@ -602,8 +604,13 @@ namespace SourceGit.Models public class SubmoduleDiff { + public string FullPath { get; set; } = string.Empty; public RevisionSubmodule Old { get; set; } = null; public RevisionSubmodule New { get; set; } = null; + + public bool CanOpenDetails => File.Exists(Path.Combine(FullPath, ".git")) && + Old != null && Old.Commit.Author != User.Invalid && + New != null && New.Commit.Author != User.Invalid; } public class DiffResult diff --git a/src/Models/RevisionFile.cs b/src/Models/RevisionFile.cs index 29a23efa..9a9fe7d2 100644 --- a/src/Models/RevisionFile.cs +++ b/src/Models/RevisionFile.cs @@ -39,5 +39,6 @@ namespace SourceGit.Models { public Commit Commit { get; set; } = null; public CommitFullMessage FullMessage { get; set; } = null; + public int UncommittedChanges { get; set; } = 0; } } diff --git a/src/Native/Linux.cs b/src/Native/Linux.cs index 881c9bcf..e411cbce 100644 --- a/src/Native/Linux.cs +++ b/src/Native/Linux.cs @@ -33,6 +33,21 @@ namespace SourceGit.Native } } + public void HideSelf() + { + // Do Nothing. Never used. + } + + public void HideOtherApplications() + { + // Do Nothing. Never used. + } + + public void ShowAllApplications() + { + // Do Nothing. Never used. + } + public string GetDataDir() { // AppImage supports portable mode diff --git a/src/Native/MacOS.cs b/src/Native/MacOS.cs index fe0c0475..6431d771 100644 --- a/src/Native/MacOS.cs +++ b/src/Native/MacOS.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Runtime.InteropServices; using System.Runtime.Versioning; using Avalonia; @@ -13,6 +14,18 @@ namespace SourceGit.Native [SupportedOSPlatform("macOS")] internal class MacOS : OS.IBackend { + [DllImport("/usr/lib/libobjc.dylib", EntryPoint = "objc_getClass")] + public static extern IntPtr objc_getClass(string name); + + [DllImport("/usr/lib/libobjc.dylib", EntryPoint = "sel_registerName")] + public static extern IntPtr sel_registerName(string name); + + [DllImport("/usr/lib/libobjc.dylib", EntryPoint = "objc_msgSend")] + public static extern IntPtr objc_msgSend(IntPtr receiver, IntPtr selector); + + [DllImport("/usr/lib/libobjc.dylib", EntryPoint = "objc_msgSend")] + public static extern IntPtr objc_msgSendWithArg(IntPtr receiver, IntPtr selector, IntPtr arg); + public void SetupApp(AppBuilder builder) { builder.With(new MacOSPlatformOptions() @@ -44,6 +57,39 @@ namespace SourceGit.Native window.ExtendClientAreaToDecorationsHint = true; } + public void HideSelf() + { + IntPtr nsApplicationClass = objc_getClass("NSApplication"); + IntPtr nsSharedApplicationSelector = sel_registerName("sharedApplication"); + IntPtr nsApp = objc_msgSend(nsApplicationClass, nsSharedApplicationSelector); + IntPtr nsMethodSelector = sel_registerName("hide:"); + IntPtr nsDelegateSelector = sel_registerName("delegate"); + IntPtr nsDelegate = objc_msgSend(nsApp, nsDelegateSelector); + objc_msgSendWithArg(nsApp, nsMethodSelector, nsDelegate); + } + + public void HideOtherApplications() + { + IntPtr nsApplicationClass = objc_getClass("NSApplication"); + IntPtr nsSharedApplicationSelector = sel_registerName("sharedApplication"); + IntPtr nsApp = objc_msgSend(nsApplicationClass, nsSharedApplicationSelector); + IntPtr nsMethodSelector = sel_registerName("hideOtherApplications:"); + IntPtr nsDelegateSelector = sel_registerName("delegate"); + IntPtr nsDelegate = objc_msgSend(nsApp, nsDelegateSelector); + objc_msgSendWithArg(nsApp, nsMethodSelector, nsDelegate); + } + + public void ShowAllApplications() + { + IntPtr nsApplicationClass = objc_getClass("NSApplication"); + IntPtr nsSharedApplicationSelector = sel_registerName("sharedApplication"); + IntPtr nsApp = objc_msgSend(nsApplicationClass, nsSharedApplicationSelector); + IntPtr nsMethodSelector = sel_registerName("unhideAllApplications:"); + IntPtr nsDelegateSelector = sel_registerName("delegate"); + IntPtr nsDelegate = objc_msgSend(nsApp, nsDelegateSelector); + objc_msgSendWithArg(nsApp, nsMethodSelector, nsDelegate); + } + public string GetDataDir() { return Path.Combine( diff --git a/src/Native/OS.cs b/src/Native/OS.cs index 7c12287a..6f899f23 100644 --- a/src/Native/OS.cs +++ b/src/Native/OS.cs @@ -18,6 +18,10 @@ namespace SourceGit.Native void SetupApp(AppBuilder builder); void SetupWindow(Window window); + void HideSelf(); + void HideOtherApplications(); + void ShowAllApplications(); + string GetDataDir(); string FindGitExecutable(); string FindTerminal(Models.ShellOrTerminal shell); @@ -154,6 +158,21 @@ namespace SourceGit.Native _backend.SetupWindow(window); } + public static void HideSelf() + { + _backend.HideSelf(); + } + + public static void HideOtherApplications() + { + _backend.HideOtherApplications(); + } + + public static void ShowAllApplications() + { + _backend.ShowAllApplications(); + } + public static void LogException(Exception ex) { if (ex == null) diff --git a/src/Native/Windows.cs b/src/Native/Windows.cs index e71c0de7..35b8aba6 100644 --- a/src/Native/Windows.cs +++ b/src/Native/Windows.cs @@ -58,6 +58,21 @@ namespace SourceGit.Native window.BorderThickness = new Thickness(1); } + public void HideSelf() + { + // Do Nothing. Never used. + } + + public void HideOtherApplications() + { + // Do Nothing. Never used. + } + + public void ShowAllApplications() + { + // Do Nothing. Never used. + } + public string GetDataDir() { var execFile = Process.GetCurrentProcess().MainModule!.FileName; diff --git a/src/Resources/Locales/de_DE.axaml b/src/Resources/Locales/de_DE.axaml index 7db1f934..d4ecd50b 100644 --- a/src/Resources/Locales/de_DE.axaml +++ b/src/Resources/Locales/de_DE.axaml @@ -425,7 +425,6 @@ $1, $2, … Werte der Eingabe-Steuerelemente FLOW – Hotfix fertigstellen FLOW – Release fertigstellen Ziel: - Push zu Remote(s) nach Abschluss Squash beim Merge Hotfix: Hotfix-Präfix: diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index a14c66a1..efd003af 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -23,6 +23,7 @@ Use AI to generate commit message Use Hide SourceGit + Hide Others Show All Patch 3-Way Merge @@ -130,6 +131,8 @@ Clone Remote Repository Extra Parameters: Additional arguments to clone repository. Optional. + Bookmark: + Group: Local Name: Repository name. Optional. Parent Folder: @@ -275,8 +278,11 @@ Label: Options: Use '|' as delimiter for options + String Formatter: + Optional. Used to format output string. Ignored when input is empty. Please use `${VALUE}` to represent the input string. The built-in variables ${REPO}, ${REMOTE}, ${BRANCH}, ${BRANCH_FRIENDLY_NAME}, ${SHA}, ${FILE}, and ${TAG} remain available here Type: + Use Friendly Name: Workspaces Color Name @@ -370,6 +376,7 @@ SUBMODULE DELETED NEW + + {0} Uncommitted Changes Swap Syntax Highlighting Line Word Wrap @@ -433,7 +440,6 @@ FLOW - Finish Hotfix FLOW - Finish Release Target: - Push to remote(s) after performing finish Squash during merge Hotfix: Hotfix Prefix: @@ -499,6 +505,7 @@ Go to next tab Go to previous tab Create new tab + Open local repository Open Preferences dialog Show workspace dropdown menu Switch active tab @@ -592,6 +599,10 @@ Open Data Storage Directory Open File Open in External Merge Tool + Open Local Repository + Bookmark: + Group: + Folder: Optional. Create New Tab Close Tab @@ -898,6 +909,8 @@ unmerged Update URL + Submodule Change Details + OPEN DETAILS OK TAGGER TIME diff --git a/src/Resources/Locales/es_ES.axaml b/src/Resources/Locales/es_ES.axaml index be11ae58..6bcce512 100644 --- a/src/Resources/Locales/es_ES.axaml +++ b/src/Resources/Locales/es_ES.axaml @@ -27,6 +27,7 @@ Usar OpenAI para generar mensaje de commit Usar Ocultar SourceGit + Ocultar Otros Mostrar Todo Aplicar Parche Merge a 3 vías (3-Way) @@ -134,6 +135,8 @@ Clonar Repositorio Remoto Parámetros Adicionales: Argumentos adicionales para clonar el repositorio. Opcional. + Marcador: + Grupo: Nombre Local: Nombre del repositorio. Opcional. Carpeta Padre: @@ -279,8 +282,11 @@ Etiqueta: Opciones: Usar '|' como delimitador para las opciones + Formateador de Cadenas de texto: + Opcional. Utilizado para formatear la cadena de salida. Ignorado cuando la entrada está vacía. Por favor utiliza `${VALUE}` para representar la cadena de entrada. La variables incorporadas ${REPO}, ${REMOTE}, ${BRANCH}, ${BRANCH_FRIENDLY_NAME}, ${SHA}, ${FILE}, y ${TAG} permanecen disponibles aquí Tipo: + Utilizar Nombre Amigable: Espacios de Trabajo Color Nombre @@ -374,6 +380,7 @@ SUBMÓDULO BORRADO NUEVO + + {0} Cambios Sin confirmar Intercambiar Resaltado de Sintaxis Ajuste de Línea @@ -437,7 +444,6 @@ FLOW - Finalizar Hotfix FLOW - Finalizar Release Destino: - Push al/los remoto(s) después de Finalizar Squash durante el merge Hotfix: Prefijo de Hotfix: @@ -503,6 +509,7 @@ Ir a la siguiente página Ir a la página anterior Crear nueva página + Abrir repositorio local Abrir diálogo de preferencias Mostrar menú desplegable del espacio de trabajo Cambiar página activa @@ -596,6 +603,10 @@ Abrir Directorio de Datos de la App Abrir Archivo Abrir en Herramienta de Merge + Abrir Repositorio Local + Marcador: + Grupo: + Carpeta: Opcional. Crear Nueva Página Cerrar Pestaña @@ -902,6 +913,8 @@ unmerged Actualizar URL + Cambiar Detalles del Submódulo + ABRIR DETALLES OK ETIQUETADOR HORA diff --git a/src/Resources/Locales/fr_FR.axaml b/src/Resources/Locales/fr_FR.axaml index 7a62a8ac..f2d00296 100644 --- a/src/Resources/Locales/fr_FR.axaml +++ b/src/Resources/Locales/fr_FR.axaml @@ -409,7 +409,6 @@ FLOW - Terminer Hotfix FLOW - Terminer Release Cible: - Pousser vers le(s) dépôt(s) distant(s) après avoir terminé Squash lors de la fusion Hotfix: Hotfix Prefix: diff --git a/src/Resources/Locales/id_ID.axaml b/src/Resources/Locales/id_ID.axaml index f9e1b6b8..621abaa9 100644 --- a/src/Resources/Locales/id_ID.axaml +++ b/src/Resources/Locales/id_ID.axaml @@ -390,7 +390,6 @@ FLOW - Selesaikan Hotfix FLOW - Selesaikan Release Target: - Push ke remote setelah selesai Squash saat merge Hotfix: Prefix Hotfix: diff --git a/src/Resources/Locales/it_IT.axaml b/src/Resources/Locales/it_IT.axaml index df7ab6f1..629f6da4 100644 --- a/src/Resources/Locales/it_IT.axaml +++ b/src/Resources/Locales/it_IT.axaml @@ -424,7 +424,6 @@ ${pure_files:N} Come ${files:N}, ma senza cartelle FLOW - Completa Hotfix FLOW - Completa Rilascio Target: - Invia al remote dopo aver finito Esegui squash durante il merge Hotfix: Prefisso Hotfix: diff --git a/src/Resources/Locales/ja_JP.axaml b/src/Resources/Locales/ja_JP.axaml index 5d4969ab..26c1701b 100644 --- a/src/Resources/Locales/ja_JP.axaml +++ b/src/Resources/Locales/ja_JP.axaml @@ -425,7 +425,6 @@ フロー - 緊急のバグ修正を完了 フロー - リリース作業を完了 対象: - コミットの完了後、リモートにプッシュ スカッシュしてマージ 緊急のバグ修正: 緊急のバグ修正用のプレフィックス: diff --git a/src/Resources/Locales/ko_KR.axaml b/src/Resources/Locales/ko_KR.axaml index 7a278e27..fa71db59 100644 --- a/src/Resources/Locales/ko_KR.axaml +++ b/src/Resources/Locales/ko_KR.axaml @@ -392,7 +392,6 @@ FLOW - Hotfix 완료 FLOW - Release 완료 대상: - 완료 후 원격(들)에 푸시 병합 시 스쿼시 핫픽스: Hotfix 접두사: diff --git a/src/Resources/Locales/ru_RU.axaml b/src/Resources/Locales/ru_RU.axaml index 5b59a22f..9084e4cd 100644 --- a/src/Resources/Locales/ru_RU.axaml +++ b/src/Resources/Locales/ru_RU.axaml @@ -437,7 +437,6 @@ ПРОЦЕСС - Закончить исправление ПРОЦЕСС - Завершить выпуск Цель: - Выложить на удалённый(ые) после завершения Втиснуть при слиянии Исправление: Префикс исправлений: diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index b8b926bf..f5d1a64e 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -27,7 +27,8 @@ 使用AI助手生成提交信息 应用所选 隐藏 SourceGit - 显示所有窗口 + 隐藏其他 + 显示全部 应用补丁(apply) 尝试三路合并 补丁文件 : @@ -134,6 +135,8 @@ 克隆远程仓库 额外参数 : 其他克隆参数,选填。 + 书签 : + 自定义分组 : 本地仓库名 : 本地仓库目录的名字,选填。 父级目录 : @@ -279,8 +282,11 @@ 名称 : 选项列表 : 选项之间请使用英文 '|' 作为分隔符 + 输出内容格式化字串: + 可选。用于格式化输出结果。当用户输入为空时忽略该操作。请使用`${VALUE}`代替用户输入。 内置变量 ${REPO}, ${REMOTE}, ${BRANCH}, ${BRANCH_FRIENDLY_NAME}, ${SHA}, ${FILE} 与 ${TAG} 在这里仍然可用 类型 : + 输出结果带有远程名: 工作区 颜色 名称 @@ -374,6 +380,7 @@ 子模块 删除 新增 + + {0} 项未提交变更 交换比对双方 语法高亮 自动换行 @@ -437,7 +444,6 @@ 结束修复分支 结束版本分支 目标分支 : - 完成后自动推送 压缩变更为单一提交后合并分支 修复分支 : 修复分支名前缀 : @@ -503,6 +509,7 @@ 切换到下一个页面 切换到上一个页面 新建页面 + 打开本地仓库 打开偏好设置面板 显示工作区下拉菜单 切换显示页面 @@ -596,6 +603,10 @@ 浏览应用数据目录 打开文件 使用外部对比工具查看 + 打开本地仓库 + 书签 : + 分组 : + 仓库位置 : 选填。 新建空白页 关闭标签页 @@ -902,6 +913,8 @@ 未解决冲突 更新 仓库 + 子模块变更详情 + 查看变更详情 确 定 创建者 创建时间 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index 4751416e..f457cf03 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -27,7 +27,8 @@ 使用 AI 產生提交訊息 套用選取 隱藏 SourceGit - 顯示所有 + 隱藏其他 + 顯示全部 套用修補檔 (apply patch) 嘗試三向合併 修補檔: @@ -134,6 +135,8 @@ 複製 (clone) 遠端存放庫 額外參數: 其他複製參數,選填。 + 書籤: + 群組: 本機存放庫名稱: 本機存放庫目錄的名稱,選填。 上層目錄: @@ -279,8 +282,11 @@ 名稱: 選項列表: 請使用英文「|」符號分隔選項 + 字串格式化器: + 可選。用於格式化輸出字串。當輸入為空時將被忽略。請使用 `${VALUE}` 來表示輸入字串。 內建變數 ${REPO}、${REMOTE}、${BRANCH}、${BRANCH_FRIENDLY_NAME}、${SHA}、${FILE} 及 ${TAG} 在此處仍可使用 類型: + 輸出包含遠端的名稱: 工作區 顏色 名稱 @@ -374,6 +380,7 @@ 子模組 已刪除 新增 + + {0} 項未提交變更 交換比對雙方 語法上色 自動換行 @@ -437,7 +444,6 @@ 完成修復分支 完成發行分支 目標分支: - 完成後自動推送 壓縮為單一提交後合併 修復分支: 修復分支前置詞: @@ -503,6 +509,7 @@ 切換到下一個頁面 切換到上一個頁面 新增頁面 + 開啟本機存放庫 開啟偏好設定面板 顯示工作區的下拉式選單 切換目前頁面 @@ -596,6 +603,10 @@ 瀏覽程式資料目錄 開啟檔案 使用外部比對工具檢視 + 開啟本機存放庫 + 書籤: + 群組: + 存放庫位置: 選填。 新增分頁 關閉分頁 @@ -902,6 +913,8 @@ 未解決的衝突 更新 存放庫 + 子模組變更詳細資訊 + 開啟變更詳細資訊 確 定 建立者 建立時間 diff --git a/src/ViewModels/BlameCommandPalette.cs b/src/ViewModels/BlameCommandPalette.cs index c277a47c..e7343087 100644 --- a/src/ViewModels/BlameCommandPalette.cs +++ b/src/ViewModels/BlameCommandPalette.cs @@ -65,14 +65,12 @@ namespace SourceGit.ViewModels Filter = string.Empty; } - public void Launch() + public Blame Launch() { _repoFiles.Clear(); _visibleFiles.Clear(); Close(); - - if (!string.IsNullOrEmpty(_selectedFile)) - App.ShowWindow(new Blame(_repo, _selectedFile, _head)); + return !string.IsNullOrEmpty(_selectedFile) ? new Blame(_repo, _selectedFile, _head) : null; } private void UpdateVisible() diff --git a/src/ViewModels/Clone.cs b/src/ViewModels/Clone.cs index 84e712fd..a215d5e2 100644 --- a/src/ViewModels/Clone.cs +++ b/src/ViewModels/Clone.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.IO; using System.Threading.Tasks; @@ -45,6 +46,28 @@ namespace SourceGit.ViewModels set => SetProperty(ref _local, value); } + public List Groups + { + get; + } + + public RepositoryNode SelectedGroup + { + get => _selectedGroup; + set => SetProperty(ref _selectedGroup, value); + } + + public List Bookmarks + { + get; + } + + public int Bookmark + { + get => _bookmark; + set => SetProperty(ref _bookmark, value); + } + public string ExtraArgs { get => _extraArgs; @@ -61,6 +84,15 @@ namespace SourceGit.ViewModels { _pageId = pageId; + Groups = new List(); + Groups.Add(new RepositoryNode { Name = "No Group (Uncategorized)", Id = string.Empty }); + SelectedGroup = Groups[0]; + CollectGroups(Groups, Preferences.Instance.RepositoryNodes); + + Bookmarks = new List(); + for (var i = 0; i < Models.Bookmarks.Brushes.Length; i++) + Bookmarks.Add(i); + var activeWorkspace = Preferences.Instance.GetActiveWorkspace(); _parentFolder = activeWorkspace?.DefaultCloneDir; if (string.IsNullOrEmpty(ParentFolder)) @@ -134,7 +166,9 @@ namespace SourceGit.ViewModels log.Complete(); - var node = Preferences.Instance.FindOrAddNodeByRepositoryPath(path, null, true); + var parent = _selectedGroup is { Id: not "" } ? _selectedGroup : null; + var node = Preferences.Instance.FindOrAddNodeByRepositoryPath(path, parent, true); + node.Bookmark = _bookmark; await node.UpdateStatusAsync(false, null); var launcher = App.GetLauncher(); @@ -153,6 +187,18 @@ namespace SourceGit.ViewModels return true; } + private void CollectGroups(List outs, List collections) + { + foreach (var node in collections) + { + if (!node.IsRepository) + { + outs.Add(node); + CollectGroups(outs, node.SubNodes); + } + } + } + private string _pageId = string.Empty; private string _remote = string.Empty; private bool _useSSH = false; @@ -160,5 +206,7 @@ namespace SourceGit.ViewModels private string _parentFolder = string.Empty; private string _local = string.Empty; private string _extraArgs = string.Empty; + private RepositoryNode _selectedGroup = null; + private int _bookmark = 0; } } diff --git a/src/ViewModels/CompareCommandPalette.cs b/src/ViewModels/CompareCommandPalette.cs index bebffe37..7eb49efd 100644 --- a/src/ViewModels/CompareCommandPalette.cs +++ b/src/ViewModels/CompareCommandPalette.cs @@ -44,13 +44,11 @@ namespace SourceGit.ViewModels Filter = string.Empty; } - public void Launch() + public Compare Launch() { _refs.Clear(); Close(); - - if (_compareTo != null) - App.ShowWindow(new Compare(_repo, _basedOn, _compareTo)); + return _compareTo != null ? new Compare(_repo, _basedOn, _compareTo) : null; } private void UpdateRefs() diff --git a/src/ViewModels/Conflict.cs b/src/ViewModels/Conflict.cs index 06ac6077..380ad68d 100644 --- a/src/ViewModels/Conflict.cs +++ b/src/ViewModels/Conflict.cs @@ -73,10 +73,9 @@ namespace SourceGit.ViewModels await _wc.UseMineAsync([_change]); } - public async Task MergeAsync() + public MergeConflictEditor CreateOpenMergeEditorRequest() { - if (CanMerge) - await App.ShowDialog(new MergeConflictEditor(_repo, _head, _change.Path)); + return CanMerge ? new MergeConflictEditor(_repo, _head, _change.Path) : null; } public async Task MergeExternalAsync() diff --git a/src/ViewModels/DiffContext.cs b/src/ViewModels/DiffContext.cs index c24ee6ce..25155771 100644 --- a/src/ViewModels/DiffContext.cs +++ b/src/ViewModels/DiffContext.cs @@ -190,7 +190,10 @@ namespace SourceGit.ViewModels } if (isSubmodule) + { + submoduleDiff.FullPath = submoduleRoot; rs = submoduleDiff; + } } if (!isSubmodule) @@ -288,6 +291,16 @@ namespace SourceGit.ViewModels private async Task QuerySubmoduleRevisionAsync(string repo, string sha) { + if (!File.Exists(Path.Combine(repo, ".git"))) + return new Models.RevisionSubmodule() { Commit = new Models.Commit() { SHA = sha } }; + + var uncommittedChangesCount = 0; + if (sha.EndsWith("-dirty", StringComparison.Ordinal)) + { + sha = sha.Substring(0, sha.Length - 6); + uncommittedChangesCount = await new Commands.CountLocalChanges(repo, true).GetResultAsync().ConfigureAwait(false); + } + var commit = await new Commands.QuerySingleCommit(repo, sha).GetResultAsync().ConfigureAwait(false); if (commit == null) return new Models.RevisionSubmodule() { Commit = new Models.Commit() { SHA = sha } }; @@ -296,7 +309,8 @@ namespace SourceGit.ViewModels return new Models.RevisionSubmodule() { Commit = commit, - FullMessage = new Models.CommitFullMessage { Message = body } + FullMessage = new Models.CommitFullMessage { Message = body }, + UncommittedChanges = uncommittedChangesCount }; } diff --git a/src/ViewModels/ExecuteCustomAction.cs b/src/ViewModels/ExecuteCustomAction.cs index d12971d0..cf652371 100644 --- a/src/ViewModels/ExecuteCustomAction.cs +++ b/src/ViewModels/ExecuteCustomAction.cs @@ -18,15 +18,22 @@ namespace SourceGit.ViewModels public string Label { get; set; } public string Placeholder { get; set; } public string Text { get; set; } + public string Formatter { get; set; } - public CustomActionControlTextBox(string label, string placeholder, string defaultValue) + public CustomActionControlTextBox(string label, string placeholder, string defaultValue, string formatter) { Label = label + ":"; Placeholder = placeholder; Text = defaultValue; + Formatter = formatter; } - public string GetValue() => Text; + public string GetValue() + { + if (string.IsNullOrEmpty(Text)) + return string.Empty; + return string.IsNullOrEmpty(Formatter) ? Text : Formatter.Replace("${VALUE}", Text); + } } public class CustomActionControlPathSelector : ObservableObject, ICustomActionControlParameter @@ -77,12 +84,7 @@ namespace SourceGit.ViewModels public string Label { get; set; } public string Description { get; set; } public List Options { get; set; } = []; - - public string Value - { - get => _value; - set => SetProperty(ref _value, value); - } + public string Value { get; set; } public CustomActionControlComboBox(string label, string description, string options) { @@ -93,13 +95,45 @@ namespace SourceGit.ViewModels if (parts.Length > 0) { Options.AddRange(parts); - _value = parts[0]; + Value = parts[0]; } } - public string GetValue() => _value; + public string GetValue() => Value; + } - private string _value = string.Empty; + public class CustomActionControlBranchSelector : ObservableObject, ICustomActionControlParameter + { + public string Label { get; set; } + public string Description { get; set; } + public List Branches { get; set; } = []; + public Models.Branch SelectedBranch { get; set; } + + public CustomActionControlBranchSelector(string label, string description, Repository repo, bool isLocal, bool useFriendlyName) + { + Label = label; + Description = description; + _useFriendlyName = useFriendlyName; + + foreach (var b in repo.Branches) + { + if (b.IsLocal == isLocal && !b.IsDetachedHead) + Branches.Add(b); + } + + if (Branches.Count > 0) + SelectedBranch = Branches[0]; + } + + public string GetValue() + { + if (SelectedBranch == null) + return string.Empty; + + return _useFriendlyName ? SelectedBranch.FriendlyName : SelectedBranch.Name; + } + + private bool _useFriendlyName = false; } public class ExecuteCustomAction : Popup @@ -124,7 +158,31 @@ namespace SourceGit.ViewModels _repo = repo; CustomAction = action; Target = scopeTarget ?? new Models.Null(); - PrepareControlParameters(); + + foreach (var ctl in CustomAction.Controls) + { + switch (ctl.Type) + { + case Models.CustomActionControlType.TextBox: + ControlParameters.Add(new CustomActionControlTextBox(ctl.Label, ctl.Description, PrepareStringByTarget(ctl.StringValue), ctl.StringFormatter)); + break; + case Models.CustomActionControlType.PathSelector: + ControlParameters.Add(new CustomActionControlPathSelector(ctl.Label, ctl.Description, ctl.BoolValue, PrepareStringByTarget(ctl.StringValue))); + break; + case Models.CustomActionControlType.CheckBox: + ControlParameters.Add(new CustomActionControlCheckBox(ctl.Label, ctl.Description, ctl.StringValue, ctl.BoolValue)); + break; + case Models.CustomActionControlType.ComboBox: + ControlParameters.Add(new CustomActionControlComboBox(ctl.Label, ctl.Description, PrepareStringByTarget(ctl.StringValue))); + break; + case Models.CustomActionControlType.LocalBranchSelector: + ControlParameters.Add(new CustomActionControlBranchSelector(ctl.Label, ctl.Description, _repo, true, false)); + break; + case Models.CustomActionControlType.RemoteBranchSelector: + ControlParameters.Add(new CustomActionControlBranchSelector(ctl.Label, ctl.Description, _repo, false, ctl.BoolValue)); + break; + } + } } public override async Task Sure() @@ -153,31 +211,10 @@ namespace SourceGit.ViewModels return true; } - private void PrepareControlParameters() - { - foreach (var ctl in CustomAction.Controls) - { - switch (ctl.Type) - { - case Models.CustomActionControlType.TextBox: - ControlParameters.Add(new CustomActionControlTextBox(ctl.Label, ctl.Description, PrepareStringByTarget(ctl.StringValue))); - break; - case Models.CustomActionControlType.PathSelector: - ControlParameters.Add(new CustomActionControlPathSelector(ctl.Label, ctl.Description, ctl.BoolValue, PrepareStringByTarget(ctl.StringValue))); - break; - case Models.CustomActionControlType.CheckBox: - ControlParameters.Add(new CustomActionControlCheckBox(ctl.Label, ctl.Description, ctl.StringValue, ctl.BoolValue)); - break; - case Models.CustomActionControlType.ComboBox: - ControlParameters.Add(new CustomActionControlComboBox(ctl.Label, ctl.Description, PrepareStringByTarget(ctl.StringValue))); - break; - } - } - } - private string PrepareStringByTarget(string org) { - org = org.Replace("${REPO}", GetWorkdir()); + var repoPath = OperatingSystem.IsWindows() ? _repo.FullPath.Replace("/", "\\") : _repo.FullPath; + org = org.Replace("${REPO}", repoPath); return Target switch { @@ -186,15 +223,10 @@ namespace SourceGit.ViewModels Models.Tag t => org.Replace("${TAG}", t.Name), Models.Remote r => org.Replace("${REMOTE}", r.Name), Models.CustomActionTargetFile f => org.Replace("${FILE}", f.File).Replace("${SHA}", f.Revision?.SHA ?? string.Empty), - _ => org + _ => org.Replace("${BRANCH}", _repo.CurrentBranch?.Name ?? "HEAD") }; } - private string GetWorkdir() - { - return OperatingSystem.IsWindows() ? _repo.FullPath.Replace("/", "\\") : _repo.FullPath; - } - private void Run(string args) { var start = new ProcessStartInfo(); diff --git a/src/ViewModels/FileHistoryCommandPalette.cs b/src/ViewModels/FileHistoryCommandPalette.cs index 8a371feb..097e8566 100644 --- a/src/ViewModels/FileHistoryCommandPalette.cs +++ b/src/ViewModels/FileHistoryCommandPalette.cs @@ -60,14 +60,12 @@ namespace SourceGit.ViewModels Filter = string.Empty; } - public void Launch() + public FileHistories Launch() { _repoFiles.Clear(); _visibleFiles.Clear(); Close(); - - if (!string.IsNullOrEmpty(_selectedFile)) - App.ShowWindow(new FileHistories(_repo, _selectedFile)); + return !string.IsNullOrEmpty(_selectedFile) ? new FileHistories(_repo, _selectedFile) : null; } private void UpdateVisible() diff --git a/src/ViewModels/GitFlowFinish.cs b/src/ViewModels/GitFlowFinish.cs index 7ed48cbf..42cf0544 100644 --- a/src/ViewModels/GitFlowFinish.cs +++ b/src/ViewModels/GitFlowFinish.cs @@ -21,12 +21,6 @@ namespace SourceGit.ViewModels set; } = false; - public bool AutoPush - { - get; - set; - } = false; - public bool KeepBranch { get; @@ -50,7 +44,7 @@ namespace SourceGit.ViewModels var prefix = _repo.GitFlow.GetPrefix(Type); var name = Branch.Name.StartsWith(prefix) ? Branch.Name.Substring(prefix.Length) : Branch.Name; - var succ = await Commands.GitFlow.FinishAsync(_repo.FullPath, Type, name, Squash, AutoPush, KeepBranch, log); + var succ = await Commands.GitFlow.FinishAsync(_repo.FullPath, Type, name, Squash, KeepBranch, log); log.Complete(); return succ; diff --git a/src/ViewModels/Histories.cs b/src/ViewModels/Histories.cs index d7a81b0f..4d0fd02f 100644 --- a/src/ViewModels/Histories.cs +++ b/src/ViewModels/Histories.cs @@ -418,22 +418,6 @@ namespace SourceGit.ViewModels _repo.ShowPopup(new DropHead(_repo, head, parent)); } - public async Task InteractiveRebaseAsync(Models.Commit commit, Models.InteractiveRebaseAction act) - { - var prefill = new InteractiveRebasePrefill(commit.SHA, act); - var start = act switch - { - Models.InteractiveRebaseAction.Squash or Models.InteractiveRebaseAction.Fixup => $"{commit.SHA}~~", - _ => $"{commit.SHA}~", - }; - - var on = await new Commands.QuerySingleCommit(_repo.FullPath, start).GetResultAsync(); - if (on == null) - _repo.SendNotification($"Can not squash current commit into parent!", true); - else - await App.ShowDialog(new InteractiveRebase(_repo, on, prefill)); - } - public async Task GetCommitFullMessageAsync(Models.Commit commit) { return await new Commands.QueryCommitFullMessage(_repo.FullPath, commit.SHA) diff --git a/src/ViewModels/InteractiveRebase.cs b/src/ViewModels/InteractiveRebase.cs index ac59dec0..b045216d 100644 --- a/src/ViewModels/InteractiveRebase.cs +++ b/src/ViewModels/InteractiveRebase.cs @@ -13,6 +13,7 @@ using CommunityToolkit.Mvvm.ComponentModel; namespace SourceGit.ViewModels { public record InteractiveRebasePrefill(string SHA, Models.InteractiveRebaseAction Action); + public record InteractiveRebaseReorderItem(string Key, InteractiveRebaseItem Item); public class InteractiveRebaseItem : ObservableObject { @@ -172,10 +173,57 @@ namespace SourceGit.ViewModels .ConfigureAwait(false); var list = new List(); + var needReorder = new List(); for (var i = 0; i < commits.Count; i++) { var c = commits[i]; - list.Add(new InteractiveRebaseItem(commits.Count - i, c.Commit, c.Message)); + var item = new InteractiveRebaseItem(commits.Count - i, c.Commit, c.Message); + var subject = c.Commit.Subject; + + if (item.OriginalOrder > 1) + { + if (subject.StartsWith("fixup! ", StringComparison.Ordinal)) + { + item.Action = Models.InteractiveRebaseAction.Fixup; + needReorder.Add(new(subject.Substring(7), item)); + continue; + } + + if (subject.StartsWith("squash! ", StringComparison.Ordinal)) + { + item.Action = Models.InteractiveRebaseAction.Squash; + needReorder.Add(new(subject.Substring(8), item)); + continue; + } + } + + var reordered = new List(); + foreach (var o in needReorder) + { + if (subject.StartsWith(o.Key, StringComparison.Ordinal)) + { + list.Add(o.Item); + reordered.Add(o); + } + } + + foreach (var k in reordered) + needReorder.Remove(k); + + list.Add(item); + } + + foreach (var v in needReorder) + { + for (var i = 0; i < list.Count; i++) + { + if (v.Item.OriginalOrder > list[i].OriginalOrder) + { + v.Item.Action = Models.InteractiveRebaseAction.Pick; // For safety, reset to pick if the target commit is not found + list.Insert(i, v.Item); + break; + } + } } var selected = list.Count > 0 ? list[0] : null; @@ -363,7 +411,18 @@ namespace SourceGit.ViewModels item.IsMessageUserEdited = false; if (item.Action == Models.InteractiveRebaseAction.Squash) - pendingMessages.Add(item.OriginalFullMessage); + { + if (item.OriginalFullMessage.StartsWith("squash! ", StringComparison.Ordinal)) + { + var firstLineEnd = item.OriginalFullMessage.IndexOf('\n'); + if (firstLineEnd > 0) + pendingMessages.Add(item.OriginalFullMessage.Substring(firstLineEnd + 1)); + } + else + { + pendingMessages.Add(item.OriginalFullMessage); + } + } hasPending = true; continue; diff --git a/src/ViewModels/Launcher.cs b/src/ViewModels/Launcher.cs index 4287cb9b..5beb3f13 100644 --- a/src/ViewModels/Launcher.cs +++ b/src/ViewModels/Launcher.cs @@ -117,7 +117,7 @@ namespace SourceGit.ViewModels return false; } - public void Quit() + public void CloseAll() { _ignoreIndexChange = true; diff --git a/src/ViewModels/OpenLocalRepository.cs b/src/ViewModels/OpenLocalRepository.cs new file mode 100644 index 00000000..5d5825b0 --- /dev/null +++ b/src/ViewModels/OpenLocalRepository.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.IO; +using System.Threading.Tasks; + +namespace SourceGit.ViewModels +{ + public class OpenLocalRepository : Popup + { + [Required(ErrorMessage = "Repository folder is required")] + [CustomValidation(typeof(OpenLocalRepository), nameof(ValidateRepoPath))] + public string RepoPath + { + get => _repoPath; + set => SetProperty(ref _repoPath, value, true); + } + + public List Groups + { + get; + } + + public RepositoryNode Group + { + get => _group; + set => SetProperty(ref _group, value); + } + + public List Bookmarks + { + get; + } + + public int Bookmark + { + get => _bookmark; + set => SetProperty(ref _bookmark, value); + } + + public OpenLocalRepository(string pageId, RepositoryNode group) + { + _pageId = pageId; + + Groups = new List(); + Groups.Add(new RepositoryNode { Name = "No Group (Uncategorized)", Id = string.Empty }); + Group = group ?? Groups[0]; + CollectGroups(Groups, Preferences.Instance.RepositoryNodes); + + Bookmarks = new List(); + for (var i = 0; i < Models.Bookmarks.Brushes.Length; i++) + Bookmarks.Add(i); + } + + public static ValidationResult ValidateRepoPath(string folder, ValidationContext _) + { + if (!Directory.Exists(folder)) + return new ValidationResult("Given path can NOT be found"); + return ValidationResult.Success; + } + + public override async Task Sure() + { + var isBare = await new Commands.IsBareRepository(_repoPath).GetResultAsync(); + var parent = _group is { Id: not "" } ? _group : null; + var repoRoot = _repoPath; + if (!isBare) + { + var test = await new Commands.QueryRepositoryRootPath(_repoPath).GetResultAsync(); + if (test.IsSuccess && !string.IsNullOrWhiteSpace(test.StdOut)) + { + repoRoot = test.StdOut.Trim(); + } + else + { + var launcher = App.GetLauncher(); + foreach (var page in launcher.Pages) + { + if (page.Node.Id.Equals(_pageId, StringComparison.Ordinal)) + { + page.Popup = new Init(page.Node.Id, _repoPath, parent, test.StdErr); + break; + } + } + + return false; + } + } + + var node = Preferences.Instance.FindOrAddNodeByRepositoryPath(repoRoot, parent, true); + node.Bookmark = _bookmark; + await node.UpdateStatusAsync(false, null); + Welcome.Instance.Refresh(); + node.Open(); + return true; + } + + private void CollectGroups(List outs, List collections) + { + foreach (var node in collections) + { + if (!node.IsRepository) + { + outs.Add(node); + CollectGroups(outs, node.SubNodes); + } + } + } + + private string _pageId = string.Empty; + private string _repoPath = string.Empty; + private RepositoryNode _group = null; + private int _bookmark = 0; + } +} diff --git a/src/ViewModels/Preferences.cs b/src/ViewModels/Preferences.cs index 04ffbeb9..c68f5559 100644 --- a/src/ViewModels/Preferences.cs +++ b/src/ViewModels/Preferences.cs @@ -629,19 +629,22 @@ namespace SourceGit.ViewModels RemoveInvalidRepositoriesRecursive(RepositoryNodes); } - public async Task UpdateAvailableAIModelsAsync() + public void UpdateAvailableAIModels() { - foreach (var service in OpenAIServices) + Task.Run(() => { - try + foreach (var service in OpenAIServices) { - await service.FetchAvailableModelsAsync(); + try + { + service.FetchAvailableModels(); + } + catch + { + // Ignore errors. + } } - catch - { - // Ignore errors. - } - } + }); } public void Save() diff --git a/src/ViewModels/Push.cs b/src/ViewModels/Push.cs index ad794fbd..2da3895b 100644 --- a/src/ViewModels/Push.cs +++ b/src/ViewModels/Push.cs @@ -58,7 +58,7 @@ namespace SourceGit.ViewModels set { if (SetProperty(ref _selectedRemoteBranch, value, true)) - IsSetTrackOptionVisible = value != null && _selectedLocalBranch.Upstream != value.FullName; + IsSetTrackOptionVisible = value != null && (value.Head == null || _selectedLocalBranch.Upstream != value.FullName); } } diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index 7f91241f..6b748f35 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -1887,7 +1887,7 @@ namespace SourceGit.ViewModels try { - if (Preferences.Instance.EnableAutoFetch || !CanCreatePopup()) + if (!Preferences.Instance.EnableAutoFetch || !CanCreatePopup()) { _lastFetchTime = DateTime.Now; return; diff --git a/src/ViewModels/RepositoryCommandPalette.cs b/src/ViewModels/RepositoryCommandPalette.cs index 6d6c77c8..2b57414a 100644 --- a/src/ViewModels/RepositoryCommandPalette.cs +++ b/src/ViewModels/RepositoryCommandPalette.cs @@ -73,7 +73,6 @@ namespace SourceGit.ViewModels _cmds.Add(new("Push", "push", "Push", async () => await repo.PushAsync(false))); _cmds.Add(new("Stash.Title", "stash", "Stashes.Add", async () => await repo.StashAllAsync(false))); _cmds.Add(new("Apply.Title", "apply", "Diff", () => repo.ApplyPatch())); - _cmds.Add(new("Configure", "configure", "Settings", async () => await App.ShowDialog(new RepositoryConfigure(repo)))); _cmds.Sort((l, r) => l.Label.CompareTo(r.Label)); _visibleCmds = _cmds; diff --git a/src/ViewModels/SubmoduleRevisionCompare.cs b/src/ViewModels/SubmoduleRevisionCompare.cs new file mode 100644 index 00000000..bbf41a2d --- /dev/null +++ b/src/ViewModels/SubmoduleRevisionCompare.cs @@ -0,0 +1,177 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Avalonia.Threading; +using CommunityToolkit.Mvvm.ComponentModel; + +namespace SourceGit.ViewModels +{ + public class SubmoduleRevisionCompare : ObservableObject + { + public bool IsLoading + { + get => _isLoading; + private set => SetProperty(ref _isLoading, value); + } + + public Models.Commit Base + { + get => _base; + private set => SetProperty(ref _base, value); + } + + public Models.Commit To + { + get => _to; + private set => SetProperty(ref _to, value); + } + + public int TotalChanges + { + get => _totalChanges; + private set => SetProperty(ref _totalChanges, value); + } + + public List VisibleChanges + { + get => _visibleChanges; + private set => SetProperty(ref _visibleChanges, value); + } + + public List SelectedChanges + { + get => _selectedChanges; + set + { + if (SetProperty(ref _selectedChanges, value)) + { + if (value is { Count: 1 }) + DiffContext = new DiffContext(_repo, new Models.DiffOption(_base.SHA, _to.SHA, value[0]), _diffContext); + else + DiffContext = null; + } + } + } + + public string SearchFilter + { + get => _searchFilter; + set + { + if (SetProperty(ref _searchFilter, value)) + RefreshVisible(); + } + } + + public DiffContext DiffContext + { + get => _diffContext; + private set => SetProperty(ref _diffContext, value); + } + + public SubmoduleRevisionCompare(Models.SubmoduleDiff diff) + { + _repo = diff.FullPath; + _base = diff.Old.Commit; + _to = diff.New.Commit; + + Refresh(); + } + + public void Swap() + { + (Base, To) = (To, Base); + Refresh(); + } + + public void ClearSearchFilter() + { + SearchFilter = string.Empty; + } + + public string GetAbsPath(string path) + { + return Native.OS.GetAbsPath(_repo, path); + } + + public void OpenInExternalDiffTool(Models.Change change) + { + new Commands.DiffTool(_repo, new Models.DiffOption(_base.SHA, _to.SHA, change)).Open(); + } + + public async Task SaveChangesAsPatchAsync(List changes, string saveTo) + { + return await Commands.SaveChangesAsPatch.ProcessRevisionCompareChangesAsync(_repo, changes, _base.SHA, _to.SHA, saveTo); + } + + private void Refresh() + { + IsLoading = true; + VisibleChanges = []; + SelectedChanges = []; + + Task.Run(async () => + { + _changes = await new Commands.CompareRevisions(_repo, _base.SHA, _to.SHA) + .ReadAsync() + .ConfigureAwait(false); + + var visible = _changes; + if (!string.IsNullOrWhiteSpace(_searchFilter)) + { + visible = new List(); + foreach (var c in _changes) + { + if (c.Path.Contains(_searchFilter, StringComparison.OrdinalIgnoreCase)) + visible.Add(c); + } + } + + Dispatcher.UIThread.Post(() => + { + TotalChanges = _changes.Count; + VisibleChanges = visible; + IsLoading = false; + + if (VisibleChanges.Count > 0) + SelectedChanges = [VisibleChanges[0]]; + else + SelectedChanges = []; + }); + }); + } + + private void RefreshVisible() + { + if (_changes == null) + return; + + if (string.IsNullOrEmpty(_searchFilter)) + { + VisibleChanges = _changes; + } + else + { + var visible = new List(); + foreach (var c in _changes) + { + if (c.Path.Contains(_searchFilter, StringComparison.OrdinalIgnoreCase)) + visible.Add(c); + } + + VisibleChanges = visible; + } + } + + private string _repo; + private bool _isLoading = true; + private Models.Commit _base = null; + private Models.Commit _to = null; + private int _totalChanges = 0; + private List _changes = null; + private List _visibleChanges = null; + private List _selectedChanges = null; + private string _searchFilter = string.Empty; + private DiffContext _diffContext = null; + } +} diff --git a/src/ViewModels/Welcome.cs b/src/ViewModels/Welcome.cs index 66bde74b..49539292 100644 --- a/src/ViewModels/Welcome.cs +++ b/src/ViewModels/Welcome.cs @@ -128,19 +128,6 @@ namespace SourceGit.ViewModels return rs.StdOut.Trim(); } - public void InitRepository(string path, RepositoryNode parent, string reason) - { - if (!Preferences.Instance.IsGitConfigured()) - { - Models.Notification.Send(null, App.Text("NotConfigured"), true); - return; - } - - var activePage = App.GetLauncher().ActivePage; - if (activePage != null && activePage.CanCreatePopup()) - activePage.Popup = new Init(activePage.Node.Id, path, parent, reason); - } - public async Task AddRepositoryAsync(string path, RepositoryNode parent, bool moveNode, bool open) { var node = Preferences.Instance.FindOrAddNodeByRepositoryPath(path, parent, moveNode); @@ -163,6 +150,19 @@ namespace SourceGit.ViewModels activePage.Popup = new Clone(activePage.Node.Id); } + public void OpenLocalRepository() + { + if (!Preferences.Instance.IsGitConfigured()) + { + Models.Notification.Send(null, App.Text("NotConfigured"), true); + return; + } + + var activePage = App.GetLauncher().ActivePage; + if (activePage != null && activePage.CanCreatePopup()) + activePage.Popup = new OpenLocalRepository(activePage.Node.Id, null); + } + public void OpenTerminal() { if (!Preferences.Instance.IsGitConfigured()) diff --git a/src/Views/AIAssistant.axaml b/src/Views/AIAssistant.axaml index d7111439..b4c496ea 100644 --- a/src/Views/AIAssistant.axaml +++ b/src/Views/AIAssistant.axaml @@ -4,16 +4,16 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:v="using:SourceGit.Views" xmlns:vm="using:SourceGit.ViewModels" - mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="120" + mc:Ignorable="d" d:DesignWidth="520" d:DesignHeight="400" x:Class="SourceGit.Views.AIAssistant" x:DataType="vm:AIAssistant" x:Name="ThisControl" Icon="/App.ico" Title="{DynamicResource Text.AIAssistant}" - Width="520" SizeToContent="Height" - CanResize="False" + Width="520" Height="400" + CanResize="True" WindowStartupLocation="CenterOwner"> - + { - App.ShowWindow(new ViewModels.Compare(repo, branches[0], branches[1])); + this.ShowWindow(new ViewModels.Compare(repo, branches[0], branches[1])); ev.Handled = true; }; menu.Items.Add(compare); @@ -819,7 +819,7 @@ namespace SourceGit.Views interactiveRebase.Click += async (_, e) => { var commit = await new Commands.QuerySingleCommit(repo.FullPath, branch.Head).GetResultAsync(); - await App.ShowDialog(new ViewModels.InteractiveRebase(repo, commit)); + await this.ShowDialogAsync(new ViewModels.InteractiveRebase(repo, commit)); e.Handled = true; }; @@ -853,7 +853,7 @@ namespace SourceGit.Views compareWithCurrent.Icon = this.CreateMenuIcon("Icons.Compare"); compareWithCurrent.Click += (_, _) => { - App.ShowWindow(new ViewModels.Compare(repo, branch, current)); + this.ShowWindow(new ViewModels.Compare(repo, branch, current)); }; var compareWith = new MenuItem(); @@ -1154,7 +1154,7 @@ namespace SourceGit.Views interactiveRebase.Click += async (_, e) => { var commit = await new Commands.QuerySingleCommit(repo.FullPath, branch.Head).GetResultAsync(); - await App.ShowDialog(new ViewModels.InteractiveRebase(repo, commit)); + await this.ShowDialogAsync(new ViewModels.InteractiveRebase(repo, commit)); e.Handled = true; }; @@ -1163,7 +1163,7 @@ namespace SourceGit.Views compareWithHead.Icon = this.CreateMenuIcon("Icons.Compare"); compareWithHead.Click += (_, _) => { - App.ShowWindow(new ViewModels.Compare(repo, branch, current)); + this.ShowWindow(new ViewModels.Compare(repo, branch, current)); }; var compareWith = new MenuItem(); diff --git a/src/Views/Clone.axaml b/src/Views/Clone.axaml index 79289ed2..a501474b 100644 --- a/src/Views/Clone.axaml +++ b/src/Views/Clone.axaml @@ -4,6 +4,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:vm="using:SourceGit.ViewModels" xmlns:v="using:SourceGit.Views" + xmlns:c="using:SourceGit.Converters" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="SourceGit.Views.Clone" x:DataType="vm:Clone"> @@ -19,7 +20,7 @@ Text="{DynamicResource Text.Clone}"/> - + - + + + + + + + + + + + + + + + + + + + + diff --git a/src/Views/CommitBaseInfo.axaml b/src/Views/CommitBaseInfo.axaml index 7af9d59b..67c1c7b6 100644 --- a/src/Views/CommitBaseInfo.axaml +++ b/src/Views/CommitBaseInfo.axaml @@ -62,7 +62,10 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Views/OpenLocalRepository.axaml.cs b/src/Views/OpenLocalRepository.axaml.cs new file mode 100644 index 00000000..151662c2 --- /dev/null +++ b/src/Views/OpenLocalRepository.axaml.cs @@ -0,0 +1,56 @@ +using System; +using System.IO; + +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Platform.Storage; + +namespace SourceGit.Views +{ + public partial class OpenLocalRepository : UserControl + { + public OpenLocalRepository() + { + InitializeComponent(); + } + + private async void OnSelectRepositoryFolder(object _1, RoutedEventArgs e) + { + if (DataContext is not ViewModels.OpenLocalRepository vm) + return; + + var topLevel = TopLevel.GetTopLevel(this); + if (topLevel == null) + return; + + var preference = ViewModels.Preferences.Instance; + var workspace = preference.GetActiveWorkspace(); + var initDir = workspace.DefaultCloneDir; + if (string.IsNullOrEmpty(initDir) || !Directory.Exists(initDir)) + initDir = preference.GitDefaultCloneDir; + + var options = new FolderPickerOpenOptions() { AllowMultiple = false }; + if (Directory.Exists(initDir)) + { + var folder = await topLevel.StorageProvider.TryGetFolderFromPathAsync(initDir); + options.SuggestedStartLocation = folder; + } + + try + { + var selected = await topLevel.StorageProvider.OpenFolderPickerAsync(options); + if (selected.Count == 1) + { + var folder = selected[0]; + vm.RepoPath = folder is { Path: { IsAbsoluteUri: true } path } ? path.LocalPath : folder?.Path.ToString(); + } + } + catch (Exception exception) + { + Models.Notification.Send(null, $"Failed to open repository: {exception.Message}", true); + } + + e.Handled = true; + } + } +} diff --git a/src/Views/Preferences.axaml.cs b/src/Views/Preferences.axaml.cs index 28cdf901..6ab603f0 100644 --- a/src/Views/Preferences.axaml.cs +++ b/src/Views/Preferences.axaml.cs @@ -206,7 +206,7 @@ namespace SourceGit.Views } var preferences = ViewModels.Preferences.Instance; - await preferences.UpdateAvailableAIModelsAsync(); + preferences.UpdateAvailableAIModels(); preferences.Save(); } @@ -386,7 +386,7 @@ namespace SourceGit.Views if (sender is CheckBox box) { ViewModels.Preferences.Instance.UseSystemWindowFrame = box.IsChecked == true; - await App.ShowDialog(new ConfirmRestart()); + await this.ShowDialogAsync(new ConfirmRestart()); } e.Handled = true; @@ -486,12 +486,7 @@ namespace SourceGit.Views if (sender is not Button { DataContext: Models.CustomAction act }) return; - var dialog = new ConfigureCustomActionControls() - { - DataContext = new ViewModels.ConfigureCustomActionControls(act.Controls) - }; - - await dialog.ShowDialog(this); + await this.ShowDialogAsync(new ViewModels.ConfigureCustomActionControls(act.Controls)); e.Handled = true; } diff --git a/src/Views/RepositoryConfigure.axaml.cs b/src/Views/RepositoryConfigure.axaml.cs index 2c5622df..bc445d2a 100644 --- a/src/Views/RepositoryConfigure.axaml.cs +++ b/src/Views/RepositoryConfigure.axaml.cs @@ -56,12 +56,7 @@ namespace SourceGit.Views if (sender is not Button { DataContext: Models.CustomAction act }) return; - var dialog = new ConfigureCustomActionControls() - { - DataContext = new ViewModels.ConfigureCustomActionControls(act.Controls) - }; - - await dialog.ShowDialog(this); + await this.ShowDialogAsync(new ViewModels.ConfigureCustomActionControls(act.Controls)); e.Handled = true; } diff --git a/src/Views/RepositoryToolbar.axaml.cs b/src/Views/RepositoryToolbar.axaml.cs index e224b1ba..72bb0462 100644 --- a/src/Views/RepositoryToolbar.axaml.cs +++ b/src/Views/RepositoryToolbar.axaml.cs @@ -138,7 +138,7 @@ namespace SourceGit.Views { if (DataContext is ViewModels.Repository repo) { - await App.ShowDialog(new ViewModels.Statistics(repo.FullPath)); + await this.ShowDialogAsync(new ViewModels.Statistics(repo.FullPath)); e.Handled = true; } } @@ -147,7 +147,7 @@ namespace SourceGit.Views { if (DataContext is ViewModels.Repository repo) { - await App.ShowDialog(new ViewModels.RepositoryConfigure(repo)); + await this.ShowDialogAsync(new ViewModels.RepositoryConfigure(repo)); e.Handled = true; } } @@ -387,7 +387,7 @@ namespace SourceGit.Views { locks.Click += async (_, e) => { - await App.ShowDialog(new ViewModels.LFSLocks(repo, repo.Remotes[0].Name)); + await this.ShowDialogAsync(new ViewModels.LFSLocks(repo, repo.Remotes[0].Name)); e.Handled = true; }; } @@ -400,7 +400,7 @@ namespace SourceGit.Views lockRemote.Header = remoteName; lockRemote.Click += async (_, e) => { - await App.ShowDialog(new ViewModels.LFSLocks(repo, remoteName)); + await this.ShowDialogAsync(new ViewModels.LFSLocks(repo, remoteName)); e.Handled = true; }; locks.Items.Add(lockRemote); @@ -494,7 +494,7 @@ namespace SourceGit.Views { if (DataContext is ViewModels.Repository repo) { - await App.ShowDialog(new ViewModels.ViewLogs(repo)); + await this.ShowDialogAsync(new ViewModels.ViewLogs(repo)); e.Handled = true; } } diff --git a/src/Views/RevisionFileTreeView.axaml.cs b/src/Views/RevisionFileTreeView.axaml.cs index 4ac1901c..e9a0e22f 100644 --- a/src/Views/RevisionFileTreeView.axaml.cs +++ b/src/Views/RevisionFileTreeView.axaml.cs @@ -472,7 +472,7 @@ namespace SourceGit.Views history.Icon = this.CreateMenuIcon("Icons.Histories"); history.Click += (_, ev) => { - App.ShowWindow(new ViewModels.DirHistories(repo, path, commit.SHA)); + this.ShowWindow(new ViewModels.DirHistories(repo, path, commit.SHA)); ev.Handled = true; }; @@ -601,7 +601,7 @@ namespace SourceGit.Views history.Icon = this.CreateMenuIcon("Icons.Histories"); history.Click += (_, ev) => { - App.ShowWindow(new ViewModels.FileHistories(repo.FullPath, file.Path, commit.SHA)); + this.ShowWindow(new ViewModels.FileHistories(repo.FullPath, file.Path, commit.SHA)); ev.Handled = true; }; @@ -611,7 +611,7 @@ namespace SourceGit.Views blame.IsEnabled = file.Type == Models.ObjectType.Blob; blame.Click += (_, ev) => { - App.ShowWindow(new ViewModels.Blame(repo.FullPath, file.Path, commit)); + this.ShowWindow(new ViewModels.Blame(repo.FullPath, file.Path, commit)); ev.Handled = true; }; diff --git a/src/Views/SubmoduleRevisionCompare.axaml b/src/Views/SubmoduleRevisionCompare.axaml new file mode 100644 index 00000000..db313e48 --- /dev/null +++ b/src/Views/SubmoduleRevisionCompare.axaml @@ -0,0 +1,193 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Views/SubmoduleRevisionCompare.axaml.cs b/src/Views/SubmoduleRevisionCompare.axaml.cs new file mode 100644 index 00000000..3ee7cee6 --- /dev/null +++ b/src/Views/SubmoduleRevisionCompare.axaml.cs @@ -0,0 +1,190 @@ +using System; +using System.IO; +using System.Text; + +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Platform.Storage; + +namespace SourceGit.Views +{ + public partial class SubmoduleRevisionCompare : ChromelessWindow + { + public SubmoduleRevisionCompare() + { + InitializeComponent(); + } + + private void OnChangeContextRequested(object sender, ContextRequestedEventArgs e) + { + if (DataContext is ViewModels.SubmoduleRevisionCompare { SelectedChanges: { Count: > 0 } selected } vm && + sender is ChangeCollectionView view) + { + var menu = new ContextMenu(); + + var patch = new MenuItem(); + patch.Header = App.Text("FileCM.SaveAsPatch"); + patch.Icon = this.CreateMenuIcon("Icons.Save"); + patch.Click += async (_, e) => + { + var storageProvider = this.StorageProvider; + if (storageProvider == null) + return; + + var options = new FilePickerSaveOptions(); + options.Title = App.Text("FileCM.SaveAsPatch"); + options.DefaultExtension = ".patch"; + options.FileTypeChoices = [new FilePickerFileType("Patch File") { Patterns = ["*.patch"] }]; + + try + { + var storageFile = await storageProvider.SaveFilePickerAsync(options); + if (storageFile != null) + { + var saveTo = storageFile.Path.LocalPath; + var succ = await vm.SaveChangesAsPatchAsync(selected, saveTo); + if (succ) + await new Alert().ShowAsync(this, "Save patch successfully.", false); + } + } + catch (Exception exception) + { + await new Alert().ShowAsync(this, $"Failed to save as patch: {exception.Message}", true); + } + + e.Handled = true; + }; + + if (selected.Count == 1) + { + var change = selected[0]; + var openWithMerger = new MenuItem(); + openWithMerger.Header = App.Text("OpenInExternalMergeTool"); + openWithMerger.Icon = this.CreateMenuIcon("Icons.OpenWith"); + openWithMerger.Tag = OperatingSystem.IsMacOS() ? "⌘+⇧+D" : "Ctrl+Shift+D"; + openWithMerger.Click += (_, ev) => + { + vm.OpenInExternalDiffTool(change); + ev.Handled = true; + }; + menu.Items.Add(openWithMerger); + + if (change.Index != Models.ChangeState.Deleted) + { + var full = vm.GetAbsPath(change.Path); + var explore = new MenuItem(); + explore.Header = App.Text("RevealFile"); + explore.Icon = this.CreateMenuIcon("Icons.Explore"); + explore.IsEnabled = File.Exists(full); + explore.Click += (_, ev) => + { + Native.OS.OpenInFileManager(full); + ev.Handled = true; + }; + menu.Items.Add(explore); + } + + menu.Items.Add(new MenuItem() { Header = "-" }); + menu.Items.Add(patch); + + var copyPath = new MenuItem(); + copyPath.Header = App.Text("CopyPath"); + copyPath.Icon = this.CreateMenuIcon("Icons.Copy"); + copyPath.Tag = OperatingSystem.IsMacOS() ? "⌘+C" : "Ctrl+C"; + copyPath.Click += async (_, ev) => + { + await this.CopyTextAsync(change.Path); + ev.Handled = true; + }; + + var copyFullPath = new MenuItem(); + copyFullPath.Header = App.Text("CopyFullPath"); + copyFullPath.Icon = this.CreateMenuIcon("Icons.Copy"); + copyFullPath.Tag = OperatingSystem.IsMacOS() ? "⌘+⇧+C" : "Ctrl+Shift+C"; + copyFullPath.Click += async (_, ev) => + { + await this.CopyTextAsync(vm.GetAbsPath(change.Path)); + ev.Handled = true; + }; + + menu.Items.Add(new MenuItem() { Header = "-" }); + menu.Items.Add(copyPath); + menu.Items.Add(copyFullPath); + } + else + { + menu.Items.Add(patch); + + var copyPath = new MenuItem(); + copyPath.Header = App.Text("CopyPath"); + copyPath.Icon = this.CreateMenuIcon("Icons.Copy"); + copyPath.Tag = OperatingSystem.IsMacOS() ? "⌘+C" : "Ctrl+C"; + copyPath.Click += async (_, ev) => + { + var builder = new StringBuilder(); + foreach (var c in selected) + builder.AppendLine(c.Path); + + await this.CopyTextAsync(builder.ToString()); + ev.Handled = true; + }; + + var copyFullPath = new MenuItem(); + copyFullPath.Header = App.Text("CopyFullPath"); + copyFullPath.Icon = this.CreateMenuIcon("Icons.Copy"); + copyFullPath.Tag = OperatingSystem.IsMacOS() ? "⌘+⇧+C" : "Ctrl+Shift+C"; + copyFullPath.Click += async (_, ev) => + { + var builder = new StringBuilder(); + foreach (var c in selected) + builder.AppendLine(vm.GetAbsPath(c.Path)); + + await this.CopyTextAsync(builder.ToString()); + ev.Handled = true; + }; + + menu.Items.Add(new MenuItem() { Header = "-" }); + menu.Items.Add(copyPath); + menu.Items.Add(copyFullPath); + } + + menu.Open(view); + } + + e.Handled = true; + } + + private async void OnChangeCollectionViewKeyDown(object sender, KeyEventArgs e) + { + if (DataContext is not ViewModels.SubmoduleRevisionCompare vm) + return; + + if (sender is not ChangeCollectionView { SelectedChanges: { Count: > 0 } selectedChanges }) + return; + + var cmdKey = OperatingSystem.IsMacOS() ? KeyModifiers.Meta : KeyModifiers.Control; + if (e.Key == Key.C && e.KeyModifiers.HasFlag(cmdKey)) + { + var builder = new StringBuilder(); + var copyAbsPath = e.KeyModifiers.HasFlag(KeyModifiers.Shift); + if (selectedChanges.Count == 1) + { + builder.Append(copyAbsPath ? vm.GetAbsPath(selectedChanges[0].Path) : selectedChanges[0].Path); + } + else + { + foreach (var c in selectedChanges) + builder.AppendLine(copyAbsPath ? vm.GetAbsPath(c.Path) : c.Path); + } + + await this.CopyTextAsync(builder.ToString()); + e.Handled = true; + } + else if (e.Key == Key.F && e.KeyModifiers == cmdKey) + { + ChangeSearchBox.Focus(); + e.Handled = true; + } + } + } +} diff --git a/src/Views/SubmodulesView.axaml.cs b/src/Views/SubmodulesView.axaml.cs index e45be0ca..c8205049 100644 --- a/src/Views/SubmodulesView.axaml.cs +++ b/src/Views/SubmodulesView.axaml.cs @@ -257,7 +257,7 @@ namespace SourceGit.Views histories.Icon = this.CreateMenuIcon("Icons.Histories"); histories.Click += (_, ev) => { - App.ShowWindow(new ViewModels.FileHistories(repo.FullPath, submodule.Path)); + this.ShowWindow(new ViewModels.FileHistories(repo.FullPath, submodule.Path)); ev.Handled = true; }; diff --git a/src/Views/TagsView.axaml.cs b/src/Views/TagsView.axaml.cs index 0e66a5cc..3878fcab 100644 --- a/src/Views/TagsView.axaml.cs +++ b/src/Views/TagsView.axaml.cs @@ -271,7 +271,7 @@ namespace SourceGit.Views compareWithHead.Icon = this.CreateMenuIcon("Icons.Compare"); compareWithHead.Click += (_, _) => { - App.ShowWindow(new ViewModels.Compare(repo, tag, repo.CurrentBranch)); + this.ShowWindow(new ViewModels.Compare(repo, tag, repo.CurrentBranch)); }; var compareWith = new MenuItem(); @@ -387,7 +387,7 @@ namespace SourceGit.Views if (based.CreatorDate > to.CreatorDate) (based, to) = (to, based); - App.ShowWindow(new ViewModels.Compare(repo, based, to)); + this.ShowWindow(new ViewModels.Compare(repo, based, to)); ev.Handled = true; }; menu.Items.Add(compare); diff --git a/src/Views/TextDiffView.axaml.cs b/src/Views/TextDiffView.axaml.cs index b7be2ef3..94940e74 100644 --- a/src/Views/TextDiffView.axaml.cs +++ b/src/Views/TextDiffView.axaml.cs @@ -98,6 +98,23 @@ namespace SourceGit.Views } } + protected override Size MeasureOverride(Size availableSize) + { + if (DataContext is not ViewModels.TextDiffContext ctx) + return new Size(0, 0); + + var typeface = new Typeface(TextArea.FontFamily); + var test = new FormattedText( + $"{ctx.Data.MaxLineNumber}", + CultureInfo.CurrentCulture, + FlowDirection.LeftToRight, + typeface, + TextArea.FontSize, + Brushes.White); + + return new Size(test.Width, 0); + } + private readonly bool _usePresenter; private readonly bool _isOld; } @@ -578,29 +595,6 @@ namespace SourceGit.Views protected override void OnDataContextChanged(EventArgs e) { base.OnDataContextChanged(e); - - if (DataContext is ViewModels.TextDiffContext ctx) - { - var typeface = new Typeface(FontFamily); - var test = new FormattedText( - $"{ctx.Data.MaxLineNumber}", - CultureInfo.CurrentCulture, - FlowDirection.LeftToRight, - typeface, - FontSize, - Brushes.White); - - var width = test.WidthIncludingTrailingWhitespace; - foreach (var margin in TextArea.LeftMargins) - { - if (margin is LineNumberMargin lineNumberMargin) - margin.Width = width; - } - - var dock = TextArea.FindDescendantOfType(); - dock?.InvalidateArrange(); - } - AutoScrollToFirstChange(); } @@ -721,6 +715,9 @@ namespace SourceGit.Views if (DataContext is not ViewModels.TextDiffContext ctx) return; + foreach (var margin in TextArea.LeftMargins) + margin.InvalidateMeasure(); + if (ctx.IsSideBySide() && !IsOld) return; @@ -1495,17 +1492,17 @@ namespace SourceGit.Views var tmpFile = Path.GetTempFileName(); if (change.WorkTree == Models.ChangeState.Untracked) { - diff.GenerateNewPatchFromSelection(change, null, selection, false, tmpFile); + diff.GenerateNewPatchFromSelection(change.Path, null, selection, false, tmpFile); } else if (chunk.Combined) { var treeGuid = await new Commands.QueryStagedFileBlobGuid(repo.FullPath, change.Path).GetResultAsync(); - diff.GeneratePatchFromSelection(change, treeGuid, selection, false, tmpFile); + diff.GeneratePatchFromSelection(change.Path, treeGuid, selection, false, tmpFile); } else { var treeGuid = await new Commands.QueryStagedFileBlobGuid(repo.FullPath, change.Path).GetResultAsync(); - diff.GeneratePatchFromSelectionSingleSide(change, treeGuid, selection, false, chunk.IsOldSide, tmpFile); + diff.GeneratePatchFromSelectionSingleSide(change.Path, treeGuid, selection, false, chunk.IsOldSide, tmpFile); } await new Commands.Apply(repo.FullPath, tmpFile, true, "nowarn", "--cache --index").ExecAsync(); @@ -1533,11 +1530,11 @@ namespace SourceGit.Views var treeGuid = await new Commands.QueryStagedFileBlobGuid(repo.FullPath, change.Path).GetResultAsync(); var tmpFile = Path.GetTempFileName(); if (change.Index == Models.ChangeState.Added) - diff.GenerateNewPatchFromSelection(change, treeGuid, selection, true, tmpFile); + diff.GenerateNewPatchFromSelection(change.Path, treeGuid, selection, true, tmpFile); else if (chunk.Combined) - diff.GeneratePatchFromSelection(change, treeGuid, selection, true, tmpFile); + diff.GeneratePatchFromSelection(change.Path, treeGuid, selection, true, tmpFile); else - diff.GeneratePatchFromSelectionSingleSide(change, treeGuid, selection, true, chunk.IsOldSide, tmpFile); + diff.GeneratePatchFromSelectionSingleSide(change.Path, treeGuid, selection, true, chunk.IsOldSide, tmpFile); await new Commands.Apply(repo.FullPath, tmpFile, true, "nowarn", "--cache --index --reverse").ExecAsync(); File.Delete(tmpFile); @@ -1564,17 +1561,17 @@ namespace SourceGit.Views var tmpFile = Path.GetTempFileName(); if (change.WorkTree == Models.ChangeState.Untracked) { - diff.GenerateNewPatchFromSelection(change, null, selection, true, tmpFile); + diff.GenerateNewPatchFromSelection(change.Path, null, selection, true, tmpFile); } else if (chunk.Combined) { var treeGuid = await new Commands.QueryStagedFileBlobGuid(repo.FullPath, change.Path).GetResultAsync(); - diff.GeneratePatchFromSelection(change, treeGuid, selection, true, tmpFile); + diff.GeneratePatchFromSelection(change.Path, treeGuid, selection, true, tmpFile); } else { var treeGuid = await new Commands.QueryStagedFileBlobGuid(repo.FullPath, change.Path).GetResultAsync(); - diff.GeneratePatchFromSelectionSingleSide(change, treeGuid, selection, true, chunk.IsOldSide, tmpFile); + diff.GeneratePatchFromSelectionSingleSide(change.Path, treeGuid, selection, true, chunk.IsOldSide, tmpFile); } await new Commands.Apply(repo.FullPath, tmpFile, true, "nowarn", "--reverse").ExecAsync(); diff --git a/src/Views/WelcomeToolbar.axaml b/src/Views/WelcomeToolbar.axaml index c218058c..91f2064f 100644 --- a/src/Views/WelcomeToolbar.axaml +++ b/src/Views/WelcomeToolbar.axaml @@ -19,7 +19,14 @@ - diff --git a/src/Views/WelcomeToolbar.axaml.cs b/src/Views/WelcomeToolbar.axaml.cs index d949c095..19894c4a 100644 --- a/src/Views/WelcomeToolbar.axaml.cs +++ b/src/Views/WelcomeToolbar.axaml.cs @@ -1,9 +1,4 @@ -using System; -using System.IO; - using Avalonia.Controls; -using Avalonia.Interactivity; -using Avalonia.Platform.Storage; namespace SourceGit.Views { @@ -13,56 +8,5 @@ namespace SourceGit.Views { InitializeComponent(); } - - private async void OpenLocalRepository(object _1, RoutedEventArgs e) - { - var activePage = App.GetLauncher().ActivePage; - if (activePage == null || !activePage.CanCreatePopup()) - return; - - var topLevel = TopLevel.GetTopLevel(this); - if (topLevel == null) - return; - - var preference = ViewModels.Preferences.Instance; - var workspace = preference.GetActiveWorkspace(); - var initDir = workspace.DefaultCloneDir; - if (string.IsNullOrEmpty(initDir) || !Directory.Exists(initDir)) - initDir = preference.GitDefaultCloneDir; - - var options = new FolderPickerOpenOptions() { AllowMultiple = false }; - if (Directory.Exists(initDir)) - { - var folder = await topLevel.StorageProvider.TryGetFolderFromPathAsync(initDir); - options.SuggestedStartLocation = folder; - } - - try - { - var selected = await topLevel.StorageProvider.OpenFolderPickerAsync(options); - if (selected.Count == 1) - { - var folder = selected[0]; - var folderPath = folder is { Path: { IsAbsoluteUri: true } path } ? path.LocalPath : folder?.Path.ToString(); - var repoPath = await ViewModels.Welcome.Instance.GetRepositoryRootAsync(folderPath); - if (!string.IsNullOrEmpty(repoPath)) - { - await ViewModels.Welcome.Instance.AddRepositoryAsync(repoPath, null, false, true); - ViewModels.Welcome.Instance.Refresh(); - } - else if (Directory.Exists(folderPath)) - { - var test = await new Commands.QueryRepositoryRootPath(folderPath).GetResultAsync(); - ViewModels.Welcome.Instance.InitRepository(folderPath, null, test.StdErr); - } - } - } - catch (Exception exception) - { - Models.Notification.Send(null, $"Failed to open repository: {exception.Message}", true); - } - - e.Handled = true; - } } } diff --git a/src/Views/WorkingCopy.axaml.cs b/src/Views/WorkingCopy.axaml.cs index febc0853..10a0d263 100644 --- a/src/Views/WorkingCopy.axaml.cs +++ b/src/Views/WorkingCopy.axaml.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using Avalonia.Controls; @@ -33,7 +34,7 @@ namespace SourceGit.Views { var repoView = this.FindAncestorOfType(); if (repoView is { DataContext: ViewModels.Repository repo }) - await App.ShowDialog(new ViewModels.AssumeUnchangedManager(repo)); + await this.ShowDialogAsync(new ViewModels.AssumeUnchangedManager(repo)); e.Handled = true; } @@ -359,7 +360,7 @@ namespace SourceGit.Views mergeBuiltin.Click += async (_, e) => { var head = await new Commands.QuerySingleCommit(repo.FullPath, "HEAD").GetResultAsync(); - await App.ShowDialog(new ViewModels.MergeConflictEditor(repo, head, change.Path)); + await this.ShowDialogAsync(new ViewModels.MergeConflictEditor(repo, head, change.Path)); e.Handled = true; }; @@ -540,7 +541,7 @@ namespace SourceGit.Views hasExtra = true; } - if (repo.IsLFSEnabled()) + if (File.Exists(path) && repo.IsLFSEnabled()) { var lfs = new MenuItem(); lfs.Header = App.Text("GitLFS"); @@ -647,7 +648,7 @@ namespace SourceGit.Views history.Icon = this.CreateMenuIcon("Icons.Histories"); history.Click += (_, e) => { - App.ShowWindow(new ViewModels.DirHistories(repo, selectedSingleFolder)); + this.ShowWindow(new ViewModels.DirHistories(repo, selectedSingleFolder)); e.Handled = true; }; @@ -661,7 +662,7 @@ namespace SourceGit.Views history.Icon = this.CreateMenuIcon("Icons.Histories"); history.Click += (_, e) => { - App.ShowWindow(new ViewModels.FileHistories(repo.FullPath, change.Path)); + this.ShowWindow(new ViewModels.FileHistories(repo.FullPath, change.Path)); e.Handled = true; }; @@ -671,7 +672,7 @@ namespace SourceGit.Views blame.Click += async (_, ev) => { var commit = await new Commands.QuerySingleCommit(repo.FullPath, "HEAD").GetResultAsync(); - App.ShowWindow(new ViewModels.Blame(repo.FullPath, change.Path, commit)); + this.ShowWindow(new ViewModels.Blame(repo.FullPath, change.Path, commit)); ev.Handled = true; }; @@ -871,7 +872,7 @@ namespace SourceGit.Views history.Icon = this.CreateMenuIcon("Icons.Histories"); history.Click += (_, e) => { - App.ShowWindow(new ViewModels.DirHistories(repo, selectedSingleFolder)); + this.ShowWindow(new ViewModels.DirHistories(repo, selectedSingleFolder)); e.Handled = true; }; @@ -927,9 +928,9 @@ namespace SourceGit.Views if (services.Count == 1) { - ai.Click += async (_, e) => + ai.Click += (_, e) => { - await App.ShowDialog(new ViewModels.AIAssistant(repo, services[0], selectedStaged)); + DoOpenAIAssistant(repo, services[0], selectedStaged); e.Handled = true; }; } @@ -941,9 +942,9 @@ namespace SourceGit.Views var item = new MenuItem(); item.Header = service.Name; - item.Click += async (_, e) => + item.Click += (_, e) => { - await App.ShowDialog(new ViewModels.AIAssistant(repo, dup, selectedStaged)); + DoOpenAIAssistant(repo, dup, selectedStaged); e.Handled = true; }; @@ -1037,7 +1038,7 @@ namespace SourceGit.Views menu.Items.Add(patch); menu.Items.Add(new MenuItem() { Header = "-" }); - if (repo.IsLFSEnabled()) + if (File.Exists(path) && repo.IsLFSEnabled()) { var lfs = new MenuItem(); lfs.Header = App.Text("GitLFS"); @@ -1118,7 +1119,7 @@ namespace SourceGit.Views history.Icon = this.CreateMenuIcon("Icons.Histories"); history.Click += (_, e) => { - App.ShowWindow(new ViewModels.DirHistories(repo, selectedSingleFolder)); + this.ShowWindow(new ViewModels.DirHistories(repo, selectedSingleFolder)); e.Handled = true; }; @@ -1132,7 +1133,7 @@ namespace SourceGit.Views history.Icon = this.CreateMenuIcon("Icons.Histories"); history.Click += (_, e) => { - App.ShowWindow(new ViewModels.FileHistories(repo.FullPath, change.Path)); + this.ShowWindow(new ViewModels.FileHistories(repo.FullPath, change.Path)); e.Handled = true; }; @@ -1142,7 +1143,7 @@ namespace SourceGit.Views blame.Click += async (_, e) => { var commit = await new Commands.QuerySingleCommit(repo.FullPath, "HEAD").GetResultAsync(); - App.ShowWindow(new ViewModels.Blame(repo.FullPath, change.Path, commit)); + this.ShowWindow(new ViewModels.Blame(repo.FullPath, change.Path, commit)); e.Handled = true; }; @@ -1262,7 +1263,7 @@ namespace SourceGit.Views history.Icon = this.CreateMenuIcon("Icons.Histories"); history.Click += (_, e) => { - App.ShowWindow(new ViewModels.DirHistories(repo, selectedSingleFolder)); + this.ShowWindow(new ViewModels.DirHistories(repo, selectedSingleFolder)); e.Handled = true; }; @@ -1369,5 +1370,16 @@ namespace SourceGit.Views menu.Items.Add(custom); menu.Items.Add(new MenuItem() { Header = "-" }); } + + private void DoOpenAIAssistant(ViewModels.Repository repo, AI.Service serivce, List changes) + { + var owner = TopLevel.GetTopLevel(this) as Window; + if (owner == null) + return; + + var assistant = new ViewModels.AIAssistant(repo, serivce, changes); + var view = new AIAssistant() { DataContext = assistant }; + view.Show(owner); + } } }